diff options
Diffstat (limited to 'tools/EventClients')
140 files changed, 21147 insertions, 0 deletions
diff --git a/tools/EventClients/Clients/AppleRemote/AppleRemote.cpp b/tools/EventClients/Clients/AppleRemote/AppleRemote.cpp new file mode 100644 index 0000000000..1cd8baa730 --- /dev/null +++ b/tools/EventClients/Clients/AppleRemote/AppleRemote.cpp @@ -0,0 +1,645 @@ +/* + * AppleRemote.cpp + * AppleRemote + * + * + */ + +#include <stdio.h> +#include <getopt.h> +#include <unistd.h> +#include <stdlib.h> +#include <ctype.h> +#include <sys/errno.h> +#include <sysexits.h> +#include <mach/mach.h> +#include <mach/mach_error.h> +#include <assert.h> +#include <errno.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> +#include <sys/sysctl.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include "AppleRemote.h" +#include "../../lib/c++/xbmcclient.h" + +#define DEFAULT_MAX_CLICK_DURATION 0.5 + +#define LOG if (m_bVerbose) printf + +#define APPLICATION_NAME "XBMC" + +enum { + IR_Select, + IR_SelectHold, + IR_Right, + IR_Left, + IR_Up, + IR_Down, + IR_RightHold, + IR_LeftHold, + IR_Menu, + IR_MenuHold +}; +enum { + IR_Event_Term_ATV1X = 5, + IR_Event_Term_ATV20X = 5, + IR_Event_Term_ATV21 = 8, + IR_Event_Term_10_4 = 5, + IR_Event_Term_10_5 = 18 +}; +// magic HID key cookies AppleTV running r1.x (same as 10.4) +static std::string key_cookiesATV1X[] = +{ + "8_", //SelectHold = "18_" + "18_", + "9_", + "10_", + "12_", + "13_", + "4_", + "3_", + "7_", + "5_" +}; +// magic HID key cookies AppleTV running r2.0x +static std::string key_cookiesATV20X[] = +{ + "8_", //SelectHold = "18_" + "18_", + "9_", + "10_", + "12_", + "13_", + "4_", + "3_", + "5_", + "7_" +}; +// magic HID key cookies for AppleTV running r2.1 +static std::string key_cookiesATV21[] = +{ + "9_", //SelectHold = "19_" + "19_", + "10_", + "11_", + "13_", + "14_", + "5_", + "4_", + "6_", + "8_" +}; +// magic HID key cookies for 10.4 +static std::string key_cookies10_4[] = +{ + "8_", //SelectHold = "18_" + "18_", + "9_", + "10_", + "12_", + "13_", + "4_", + "3_", + "7_", + "5_" +}; + +// magic HID key cookies for 10.5 +static std::string key_cookies10_5[] = +{ + "21_", //SelectHold = "35_" + "35_", + "22_", + "23_", + "29_", + "30_", + "4_", + "3_", + "20_", + "18_" +}; + +AppleRemote::AppleRemote() :m_bVerbose(false), + m_remoteMode(REMOTE_NORMAL), + m_dMaxClickDuration(DEFAULT_MAX_CLICK_DURATION), + m_socket(-1), + m_timer(NULL) +{ + m_serverAddress = "localhost"; + m_appPath = ""; + m_appHome = ""; +} + +AppleRemote::~AppleRemote() +{ + DeInitialize(); +} + +void AppleRemote::RegisterCommand(const std::string &strSequence, CPacketBUTTON *pPacket) +{ + LOG("Registering command %s\n", strSequence.c_str()); + m_mapCommands[strSequence] = pPacket; +} + +void AppleRemote::Initialize() +{ + std::string *key; + std::string prefix; + + LOG("initializing apple remote\n"); + + DeInitialize(); + + m_socket = socket(AF_INET, SOCK_DGRAM, 0); + if (m_socket < 0) + { + fprintf(stderr, "Error opening UDP socket! error: %d\n", errno); + exit(1); + } + + LOG("udp socket (%d) opened\n", m_socket); + + // Runtime Version Check + SInt32 MacVersion; + + Gestalt(gestaltSystemVersion, &MacVersion); + + if (MacVersion < 0x1050) + { + // OSX 10.4/AppleTV + size_t len = 512; + char buffer[512]; + std::string hw_model = "unknown"; + + if (sysctlbyname("hw.model", &buffer, &len, NULL, 0) == 0) + hw_model = buffer; + + if (hw_model == "AppleTV1,1") + { + FILE *inpipe; + bool atv_version_found = false; + char linebuf[1000]; + + //Find the build version of the AppleTV OS + inpipe = popen("sw_vers -buildVersion", "r"); + if (inpipe) + { + //get output + if(fgets(linebuf, sizeof(linebuf) - 1, inpipe)) + { + if( strstr(linebuf,"8N5107") || strstr(linebuf,"8N5239")) + { + // r1.0 or r1.1 + atv_version_found = true; + fprintf(stderr, "Using key code for AppleTV r1.x\n"); + key = key_cookiesATV1X; + m_button_event_terminator = IR_Event_Term_ATV1X; + } + else if (strstr(linebuf,"8N5400") || strstr(linebuf,"8N5455") || strstr(linebuf,"8N5461")) + { + // r2.0, r2.01 or r2.02 + atv_version_found = true; + fprintf(stderr, "Using key code for AppleTV r2.0x\n"); + key = key_cookiesATV20X; + m_button_event_terminator = IR_Event_Term_ATV20X; + } + else if( strstr(linebuf,"8N5519")) + { + // r2.10 + atv_version_found = true; + fprintf(stderr, "Using key code for AppleTV r2.1\n"); + key = key_cookiesATV21; + m_button_event_terminator = IR_Event_Term_ATV21; + + } + else if( strstr(linebuf,"8N5622")) + { + // r2.10 + atv_version_found = true; + fprintf(stderr, "Using key code for AppleTV r2.2\n"); + key = key_cookiesATV21; + m_button_event_terminator = IR_Event_Term_ATV21; + } + } + pclose(inpipe); + } + + if(!atv_version_found){ + //handle fallback or just exit + fprintf(stderr, "AppletTV software version could not be determined.\n"); + fprintf(stderr, "Defaulting to using key code for AppleTV r2.1\n"); + key = key_cookiesATV21; + m_button_event_terminator = IR_Event_Term_ATV21; + } + } + else + { + fprintf(stderr, "Using key code for OSX 10.4\n"); + key = key_cookies10_4; + m_button_event_terminator = IR_Event_Term_10_4; + } + } + else + { + // OSX 10.5 + fprintf(stderr, "Using key code for OSX 10.5\n"); + key = key_cookies10_5; + m_button_event_terminator = IR_Event_Term_10_5; + } + m_launch_xbmc_button = key[IR_MenuHold]; + + RegisterCommand(key[IR_Select], new CPacketBUTTON(5, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE)); + RegisterCommand(key[IR_SelectHold],new CPacketBUTTON(7, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE)); + RegisterCommand(key[IR_Right], new CPacketBUTTON(4, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE)); + RegisterCommand(key[IR_Left], new CPacketBUTTON(3, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE)); + RegisterCommand(key[IR_Up], new CPacketBUTTON(1, "JS0:AppleRemote", BTN_DOWN)); + RegisterCommand(key[IR_Down], new CPacketBUTTON(2, "JS0:AppleRemote", BTN_DOWN)); + RegisterCommand(key[IR_RightHold], new CPacketBUTTON(4, "JS0:AppleRemote", BTN_DOWN)); + RegisterCommand(key[IR_LeftHold], new CPacketBUTTON(3, "JS0:AppleRemote", BTN_DOWN)); + + RegisterCommand(key[IR_Menu], new CPacketBUTTON(6, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE)); + + // Menu Hold will be used both for sending "Back" and for starting universal remote combinations (if universal mode is on) + RegisterCommand(key[IR_MenuHold], new CPacketBUTTON(8, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE)); + + // Universal commmands: + RegisterCommand(key[IR_MenuHold] + key[IR_Down], new CPacketBUTTON("Back", "R1", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE)); + + prefix = key[IR_MenuHold] + key[IR_Select]; + RegisterCommand(prefix + key[IR_Right], new CPacketBUTTON("Info", "R1", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE)); + RegisterCommand(prefix + key[IR_Left], new CPacketBUTTON("Title", "R1", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE)); + RegisterCommand(prefix + key[IR_Up], new CPacketBUTTON("PagePlus", "R1", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE)); + RegisterCommand(prefix + key[IR_Down], new CPacketBUTTON("PageMinus", "R1", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE)); + RegisterCommand(prefix + key[IR_Select],new CPacketBUTTON("Display", "R1", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE)); + + prefix = key[IR_MenuHold] + key[IR_Up]; + + RegisterCommand(prefix + key[IR_Select],new CPacketBUTTON("Stop", "R1", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE)); + RegisterCommand(prefix + key[IR_Left], new CPacketBUTTON("Power", "R1", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE)); + RegisterCommand(prefix + key[IR_Right], new CPacketBUTTON("Zero", "R1", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE)); + RegisterCommand(prefix + key[IR_Up], new CPacketBUTTON("Play", "R1", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE)); + RegisterCommand(prefix + key[IR_Down], new CPacketBUTTON("Pause", "R1", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE)); + + // for universal mode - some keys are part of a key combination. + // when those keys are pressed, we'll wait for more to come - until a valid combination was entered or a timeout expired. + // the keys "leading" a combination are added to the prefix vector. + // all we need to do to turn universal mode off is clear the prefix vector and then every command will be sent immidiately. + if (m_remoteMode == REMOTE_UNIVERSAL) + { + LOG("universal mode on. registering prefixes: %s\n", key[IR_MenuHold].c_str()); + m_universalPrefixes.push_back( key[IR_MenuHold] ); + } + else + { + LOG("universal mode off.\n"); + m_universalPrefixes.clear(); + } + + m_strCombination.clear(); + m_bSendUpRequired = false; +} + +void AppleRemote::DeInitialize() +{ + LOG("uninitializing apple remote\n"); + + ResetTimer(); + + if (m_socket > 0) + close(m_socket); + + m_socket = -1; + + LOG("deleting commands map\n"); + std::map<std::string, CPacketBUTTON *>::iterator iter = m_mapCommands.begin(); + while (iter != m_mapCommands.end()) + { + delete iter->second; + iter++; + } + m_mapCommands.clear(); + m_strCombination.clear(); +} + +void AppleRemote::SetTimer() +{ + if (m_timer) + ResetTimer(); + + LOG("setting timer to expire in %.2f secs. \n", m_dMaxClickDuration); + CFRunLoopTimerContext context = { 0, this, 0, 0, 0 }; + m_timer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + m_dMaxClickDuration, 0, 0, 0, TimerCallBack, &context); + CFRunLoopAddTimer(CFRunLoopGetCurrent(), m_timer, kCFRunLoopCommonModes); +} + +void AppleRemote::ResetTimer() +{ + if (m_timer) + { + LOG("removing timer\n"); + + CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), m_timer, kCFRunLoopCommonModes); + CFRunLoopTimerInvalidate(m_timer); + CFRelease(m_timer); + } + + m_timer = NULL; +} + +void AppleRemote::SetMaxClickDuration(double dDuration) +{ + m_dMaxClickDuration = dDuration; + LOG("setting click max duration to %.2f seconds\n", dDuration); +} + +void AppleRemote::TimerCallBack (CFRunLoopTimerRef timer, void *info) +{ + if (!info) + { + fprintf(stderr, "Error. invalid argument to timer callback\n"); + return; + } + + AppleRemote *pRemote = (AppleRemote *)info; + pRemote->SendCommand(pRemote->m_strCombination); + pRemote->m_strCombination.clear(); + pRemote->ResetTimer(); +} + +bool AppleRemote::SendCommand(const std::string &key) +{ + CPacketBUTTON *pPacket = m_mapCommands[key]; + if (pPacket) + { + LOG("Sending command for Key (%s)\n", key.c_str()); + CAddress addr(m_serverAddress.c_str()); + pPacket->Send(m_socket, addr); + + if ( 0 == (pPacket->GetFlags() & BTN_NO_REPEAT) ) + m_bSendUpRequired = true; + else + m_bSendUpRequired = false; + } + + return pPacket != NULL; +} + +void AppleRemote::LaunchApp() +{ + // the path to xbmc.app is passed as an arg, + // use this to launch from a menu press + LOG("Trying to start XBMC: [%s]\n", m_appPath.c_str()); + if (!m_appPath.empty()) + { + std::string strCmd; + + // build a finder open command + strCmd = "XBMC_HOME="; + strCmd += m_appHome; + strCmd += " "; + strCmd += m_appPath; + strCmd += " &"; + LOG("xbmc open command: [%s]\n", strCmd.c_str()); + system(strCmd.c_str()); + } +} + +void AppleRemote::SendPacket(CPacketBUTTON &packet) +{ + CAddress addr(m_serverAddress.c_str()); + packet.Send(m_socket, addr); +} + +void AppleRemote::OnKeyDown(const std::string &key) +{ + if (!IsProgramRunning(APPLICATION_NAME) && (m_serverAddress == "localhost" || m_serverAddress == "127.0.0.1")) + { + if (key == m_launch_xbmc_button) + LaunchApp(); + + return; + } + + LOG("key down: %s\n", key.c_str()); + if (m_remoteMode == REMOTE_NORMAL) { + SendCommand(key); + } + else if (m_remoteMode == REMOTE_UNIVERSAL) + { + bool bCombinationStart = false; + bool bNeedTimer = false; + if (m_strCombination.empty()) + { + bNeedTimer = true; + for (size_t i=0; i<m_universalPrefixes.size(); i++) + { + if (m_universalPrefixes[i] == key) + { + LOG("start of combination (key=%s)\n", key.c_str()); + bCombinationStart = true; + break; + } + } + } + + m_strCombination += key; + + if (bCombinationStart) + SetTimer(); + else + { + if (SendCommand(m_strCombination)) + { + m_strCombination.clear(); + ResetTimer(); + } + else if (bNeedTimer) + SetTimer(); + } + } +} + +void AppleRemote::OnKeyUp(const std::string &key) +{ + LOG("key up: %s\n", key.c_str()); + if (m_bSendUpRequired) + { + LOG("sending key-up event\n"); + CPacketBUTTON btn; + CAddress addr(m_serverAddress.c_str()); + btn.Send(m_socket, addr); + } + else + { + LOG("no need to send UP event\n"); + } +} + +void AppleRemote::SetVerbose(bool bVerbose) +{ + m_bVerbose = bVerbose; +} + +bool AppleRemote::IsVerbose() +{ + return m_bVerbose; +} + +void AppleRemote::SetMaxClickTimeout(double dTimeout) +{ + m_dMaxClickDuration = dTimeout; +} + +void AppleRemote::SetServerAddress(const std::string &strAddress) +{ + m_serverAddress = strAddress; +} + +void AppleRemote::SetAppPath(const std::string &strAddress) +{ + m_appPath = strAddress; +} + +void AppleRemote::SetAppHome(const std::string &strAddress) +{ + m_appHome = strAddress; +} + +const std::string &AppleRemote::GetServerAddress() +{ + return m_serverAddress; +} + +void AppleRemote::SetRemoteMode(RemoteModes mode) +{ + m_remoteMode = mode; +} + +bool AppleRemote::IsProgramRunning(const char* strProgram, int ignorePid) +{ + kinfo_proc* mylist = (kinfo_proc *)malloc(sizeof(kinfo_proc)); + size_t mycount = 0; + bool ret = false; + + GetBSDProcessList(&mylist, &mycount); + for(size_t k = 0; k < mycount && ret == false; k++) + { + kinfo_proc *proc = NULL; + proc = &mylist[k]; + + //LOG("proc->kp_proc.p_comm: %s\n", proc->kp_proc.p_comm); + // Process names are at most sixteen characters long. + if (strncmp(proc->kp_proc.p_comm, strProgram, 16) == 0) + { + if (ignorePid == 0 || ignorePid != proc->kp_proc.p_pid) + { + //LOG("found: %s\n", proc->kp_proc.p_comm); + ret = true; + } + } + } + free(mylist); + return ret; +} + +int AppleRemote::GetBSDProcessList(kinfo_proc **procList, size_t *procCount) + // Returns a list of all BSD processes on the system. This routine + // allocates the list and puts it in *procList and a count of the + // number of entries in *procCount. You are responsible for freeing + // this list (use "free" from System framework). + // On success, the function returns 0. + // On error, the function returns a BSD errno value. +{ + int err; + kinfo_proc * result; + bool done; + static const int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; + // Declaring name as const requires us to cast it when passing it to + // sysctl because the prototype doesn't include the const modifier. + size_t length; + + assert( procList != NULL); + assert(procCount != NULL); + + *procCount = 0; + + // We start by calling sysctl with result == NULL and length == 0. + // That will succeed, and set length to the appropriate length. + // We then allocate a buffer of that size and call sysctl again + // with that buffer. If that succeeds, we're done. If that fails + // with ENOMEM, we have to throw away our buffer and loop. Note + // that the loop causes use to call sysctl with NULL again; this + // is necessary because the ENOMEM failure case sets length to + // the amount of data returned, not the amount of data that + // could have been returned. + + result = NULL; + done = false; + do { + assert(result == NULL); + + // Call sysctl with a NULL buffer. + + length = 0; + err = sysctl( (int *) name, (sizeof(name) / sizeof(*name)) - 1, + NULL, &length, + NULL, 0); + if (err == -1) { + err = errno; + } + + // Allocate an appropriately sized buffer based on the results + // from the previous call. + + if (err == 0) { + result = (kinfo_proc* )malloc(length); + if (result == NULL) { + err = ENOMEM; + } + } + + // Call sysctl again with the new buffer. If we get an ENOMEM + // error, toss away our buffer and start again. + + if (err == 0) { + err = sysctl( (int *) name, (sizeof(name) / sizeof(*name)) - 1, + result, &length, + NULL, 0); + if (err == -1) { + err = errno; + } + if (err == 0) { + done = true; + } else if (err == ENOMEM) { + assert(result != NULL); + free(result); + result = NULL; + err = 0; + } + } + } while (err == 0 && ! done); + + // Clean up and establish post conditions. + + if (err != 0 && result != NULL) { + free(result); + result = NULL; + } + *procList = result; + if (err == 0) { + *procCount = length / sizeof(kinfo_proc); + } + + assert( (err == 0) == (*procList != NULL) ); + + return err; +} + +int AppleRemote::GetButtonEventTerminator(void) +{ + return(m_button_event_terminator); +} diff --git a/tools/EventClients/Clients/AppleRemote/AppleRemote.h b/tools/EventClients/Clients/AppleRemote/AppleRemote.h new file mode 100644 index 0000000000..45e3a427bc --- /dev/null +++ b/tools/EventClients/Clients/AppleRemote/AppleRemote.h @@ -0,0 +1,80 @@ +/* + * AppleRemote.h + * AppleRemote + * + * + */ +#ifndef __APPLE__REMOTE__H__ +#define __APPLE__REMOTE__H__ + +#include <ctype.h> +#include <Carbon/Carbon.h> + +#include <vector> +#include <map> +#include <string> + +typedef enum { REMOTE_NONE, REMOTE_NORMAL, REMOTE_UNIVERSAL } RemoteModes; +typedef struct kinfo_proc kinfo_proc; + +class CPacketBUTTON; +class AppleRemote +{ +public: + AppleRemote(); + virtual ~AppleRemote(); + + void Initialize(); + void DeInitialize(); + + void ResetTimer(); + void SetTimer(); + + void OnKeyDown(const std::string &key); + void OnKeyUp(const std::string &key); + + int GetButtonEventTerminator(void); + + void SetMaxClickDuration(double dDuration); + + void SetVerbose(bool bVerbose); + bool IsVerbose(); + + void SetMaxClickTimeout(double dTimeout); + void SetServerAddress(const std::string &strAddress); + void SetAppPath(const std::string &strAddress); + void SetAppHome(const std::string &strAddress); + void SetRemoteMode(RemoteModes mode); + + const std::string &GetServerAddress(); + + static int GetBSDProcessList(kinfo_proc **procList, size_t *procCount); + bool IsProgramRunning(const char* strProgram, int ignorePid=0); + void LaunchApp(); + + void SendPacket(CPacketBUTTON &packet); + +protected: + static void TimerCallBack (CFRunLoopTimerRef timer, void *info); + void RegisterCommand(const std::string &strSequence, CPacketBUTTON *pPacket); + bool SendCommand(const std::string &key); + void ParseConfig(); + + bool m_bVerbose; + bool m_bSendUpRequired; + RemoteModes m_remoteMode; + double m_dMaxClickDuration; + int m_socket; + std::string m_strCombination; + std::string m_serverAddress; + std::string m_appPath; + std::string m_appHome; + + std::map<std::string, CPacketBUTTON *> m_mapCommands; + std::vector<std::string> m_universalPrefixes; + std::string m_launch_xbmc_button; + int m_button_event_terminator; + CFRunLoopTimerRef m_timer; +}; + +#endif diff --git a/tools/EventClients/Clients/AppleRemote/Makefile b/tools/EventClients/Clients/AppleRemote/Makefile new file mode 100644 index 0000000000..63518afe16 --- /dev/null +++ b/tools/EventClients/Clients/AppleRemote/Makefile @@ -0,0 +1,25 @@ +CXX=g++ +CC=g++ + +CFLAGS+=-isysroot /Developer/SDKs/MacOSX10.4u.sdk -mmacosx-version-min=10.4 +CFLAGS+=-Wall +LDFLAGS+=-framework IOKit -framework Carbon -framework ForceFeedback + +OBJS = iremoted.o AppleRemote.o + +TARGET = ../../../osx/XBMCHelper +CLEAN_FILES=$(TARGET) + +all: $(TARGET) + +$(TARGET): $(OBJS) + g++ $(LDFLAGS) $(OBJS) -o $(TARGET) + +.cpp.o: + $(CXX) -c $(CFLAGS) $(DEFINES) $(INCLUDES) $< -o ${<:.cpp=.o} + +.c.o: + $(CC) -c $(CFLAGS) $(DEFINES) $(INCLUDES) $< -o ${<:.c=.o} + +clean: + $(RM) -rf *.o ../../../osx/XBMCHelper diff --git a/tools/EventClients/Clients/AppleRemote/XBox360.h b/tools/EventClients/Clients/AppleRemote/XBox360.h new file mode 100644 index 0000000000..88d6a0cb94 --- /dev/null +++ b/tools/EventClients/Clients/AppleRemote/XBox360.h @@ -0,0 +1,576 @@ +#ifndef __XBOX360_H__ +#define __XBOX360_H__ + +#include <IOKit/usb/IOUSBLib.h> +#include <IOKit/IOKitLib.h> +#include <IOKit/IOCFPlugIn.h> +#include <IOKit/hid/IOHIDLib.h> +#include <IOKit/hid/IOHIDKeys.h> +#include <IOKit/hid/IOHIDUsageTables.h> + +#include <ForceFeedback/ForceFeedback.h> + +#include <pthread.h> +#include <string> +#include <list> + +#include "AppleRemote.h" +#include "../../lib/c++/xbmcclient.h" + +#define SAFELY(expr) if ((expr) != kIOReturnSuccess) { printf("ERROR: \"%s\".\n", #expr); return; } + +extern AppleRemote g_appleRemote; + +class XBox360Controller +{ + public: + + static XBox360Controller* XBox360Controller::Create(io_service_t device, int deviceNum, bool deviceWireless) + { + XBox360Controller* controller = new XBox360Controller(); + IOReturn ret; + IOCFPlugInInterface **plugInInterface; + SInt32 score=0; + + ret = IOCreatePlugInInterfaceForService(device, kIOHIDDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score); + if (ret == kIOReturnSuccess) + { + ret = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID122), (LPVOID*)&controller->hidDevice); + (*plugInInterface)->Release(plugInInterface); + if (ret == kIOReturnSuccess) + { + controller->forceFeedback=0; + FFCreateDevice(device, &controller->forceFeedback); + controller->index = deviceNum; + controller->deviceHandle = device; + controller->wireless = deviceWireless; + + char str[128]; + sprintf(str, "%s Controller %d", deviceWireless ? "Wireless" : "Wired", deviceNum); + controller->name = str; + + return controller; + } + } + + return 0; + } + + ~XBox360Controller() + { + if (deviceHandle != 0) IOObjectRelease(deviceHandle); + if (hidDevice != 0) (*hidDevice)->Release(hidDevice); + if (forceFeedback != 0) FFReleaseDevice(forceFeedback); + } + + void start() + { + int i,j; + CFRunLoopSourceRef eventSource; + + // Get serial. + FFEFFESCAPE escape; + unsigned char c; + std::string serial; + + serial = getSerialNumber(); + //printf("SERIAL: %s, forceFeedback: 0x%08lx\n", serial.c_str(), forceFeedback); + + CFArrayRef elements; + SAFELY((*hidDevice)->copyMatchingElements(hidDevice, NULL, &elements)); + + for(i=0; i<CFArrayGetCount(elements); i++) + { + CFDictionaryRef element = (CFDictionaryRef)CFArrayGetValueAtIndex(elements, i); + long usage, usagePage, longCookie; + + // Get cookie. + if (getVal(element, CFSTR(kIOHIDElementCookieKey), longCookie) && + getVal(element, CFSTR(kIOHIDElementUsageKey), usage) && + getVal(element, CFSTR(kIOHIDElementUsagePageKey), usagePage)) + { + IOHIDElementCookie cookie = (IOHIDElementCookie)longCookie; + + // Match up items + switch (usagePage) + { + case 0x01: // Generic Desktop + j=0; + switch (usage) + { + case 0x35: j++; // Right trigger + case 0x32: j++; // Left trigger + case 0x34: j++; // Right stick Y + case 0x33: j++; // Right stick X + case 0x31: j++; // Left stick Y + case 0x30: // Left stick X + axis[j] = cookie; + break; + default: + break; + } + break; + + case 0x09: // Button + if (usage >= 1 && usage <= 15) + { + // Button 1-11 + buttons[usage-1] = cookie; + } + break; + + default: + break; + } + } + } + + // Start queue. + SAFELY((*hidDevice)->open(hidDevice, 0)); + + hidQueue = (*hidDevice)->allocQueue(hidDevice); + if (hidQueue == NULL) + { + printf("Unable to allocate queue\n"); + return; + } + + // Create queue, set callback. + SAFELY((*hidQueue)->create(hidQueue, 0, 32)); + SAFELY((*hidQueue)->createAsyncEventSource(hidQueue, &eventSource)); + SAFELY((*hidQueue)->setEventCallout(hidQueue, CallbackFunction, this, NULL)); + + // Add to runloop. + CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopCommonModes); + + // Add the elements. + for(i=0; i<6; i++) + (*hidQueue)->addElement(hidQueue, axis[i], 0); + for(i=0; i<15; i++) + (*hidQueue)->addElement(hidQueue, buttons[i], 0); + + // Start. + SAFELY((*hidQueue)->start(hidQueue)); + +#if 0 + // Read existing properties + { +// CFDictionaryRef dict=(CFDictionaryRef)IORegistryEntryCreateCFProperty(registryEntry,CFSTR("DeviceData"),NULL,0); + CFDictionaryRef dict = (CFDictionaryRef)[GetController(GetSerialNumber(registryEntry)) retain]; + if(dict!=0) { + CFBooleanRef boolValue; + CFNumberRef intValue; + + if(CFDictionaryGetValueIfPresent(dict,CFSTR("InvertLeftX"),(void*)&boolValue)) { + [leftStickInvertX setState:CFBooleanGetValue(boolValue)?NSOnState:NSOffState]; + } else NSLog(@"No value for InvertLeftX"); + if(CFDictionaryGetValueIfPresent(dict,CFSTR("InvertLeftY"),(void*)&boolValue)) { + [leftStickInvertY setState:CFBooleanGetValue(boolValue)?NSOnState:NSOffState]; + } else NSLog(@"No value for InvertLeftY"); + if(CFDictionaryGetValueIfPresent(dict,CFSTR("RelativeLeft"),(void*)&boolValue)) { + BOOL enable=CFBooleanGetValue(boolValue); + [leftLinked setState:enable?NSOnState:NSOffState]; + [leftStick setLinked:enable]; + } else NSLog(@"No value for RelativeLeft"); + if(CFDictionaryGetValueIfPresent(dict,CFSTR("DeadzoneLeft"),(void*)&intValue)) { + UInt16 i; + + CFNumberGetValue(intValue,kCFNumberShortType,&i); + [leftStickDeadzone setIntValue:i]; + [leftStick setDeadzone:i]; + } else NSLog(@"No value for DeadzoneLeft"); + if(CFDictionaryGetValueIfPresent(dict,CFSTR("InvertRightX"),(void*)&boolValue)) { + [rightStickInvertX setState:CFBooleanGetValue(boolValue)?NSOnState:NSOffState]; + } else NSLog(@"No value for InvertRightX"); + if(CFDictionaryGetValueIfPresent(dict,CFSTR("InvertRightY"),(void*)&boolValue)) { + [rightStickInvertY setState:CFBooleanGetValue(boolValue)?NSOnState:NSOffState]; + } else NSLog(@"No value for InvertRightY"); + if(CFDictionaryGetValueIfPresent(dict,CFSTR("RelativeRight"),(void*)&boolValue)) { + BOOL enable=CFBooleanGetValue(boolValue); + [rightLinked setState:enable?NSOnState:NSOffState]; + [rightStick setLinked:enable]; + } else NSLog(@"No value for RelativeRight"); + if(CFDictionaryGetValueIfPresent(dict,CFSTR("DeadzoneRight"),(void*)&intValue)) { + UInt16 i; + + CFNumberGetValue(intValue,kCFNumberShortType,&i); + [rightStickDeadzone setIntValue:i]; + [rightStick setDeadzone:i]; + } else NSLog(@"No value for DeadzoneRight"); + CFRelease(dict); + } else NSLog(@"No settings found"); + } + + // Set LED and manual motor control + // [self updateLED:0x0a]; + [self setMotorOverride:TRUE]; + [self testMotorsLarge:0 small:0]; + largeMotor=0; + smallMotor=0; + + // Battery level? + { + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSString *path; + CFTypeRef prop; + + path = nil; + if (IOObjectConformsTo(registryEntry, "WirelessHIDDevice")) + { + prop = IORegistryEntryCreateCFProperty(registryEntry, CFSTR("BatteryLevel"), NULL, 0); + if (prop != nil) + { + unsigned char level; + + if (CFNumberGetValue(prop, kCFNumberCharType, &level)) + path = [bundle pathForResource:[NSString stringWithFormat:@"batt%i", level / 64] ofType:@"tif"]; + CFRelease(prop); + } + } + if (path == nil) + path = [bundle pathForResource:@"battNone" ofType:@"tif"]; + [batteryLevel setImage:[[[NSImage alloc] initByReferencingFile:path] autorelease]]; + } + } + #endif + + c = 0x0a; + if (serial.length() > 0) + { + #if 0 + for (int i = 0; i < 4; i++) + { + if ((leds[i] == nil) || ([leds[i] caseInsensitiveCompare:serial] == NSOrderedSame)) + { + c = 0x06 + i; + if (leds[i] == nil) + { + //leds[i] = [serial retain]; + printf("Added controller with LED %i\n", i); + } + break; + } + } + #endif + } + c = 0x06; + escape.dwSize = sizeof(escape); + escape.dwCommand = 0x02; + escape.cbInBuffer = sizeof(c); + escape.lpvInBuffer = &c; + escape.cbOutBuffer = 0; + escape.lpvOutBuffer = NULL; + if (FFDeviceEscape(forceFeedback, &escape) != FF_OK) + printf("Error: FF\n"); + } + + void stop() + { + if (hidQueue != 0) + { + // Stop the queue. + (*hidQueue)->stop(hidQueue); + + // Remove from the run loop. + CFRunLoopSourceRef eventSource = (*hidQueue)->getAsyncEventSource(hidQueue); + if ((eventSource != 0) && CFRunLoopContainsSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopCommonModes)) + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopCommonModes); + + // Whack. + (*hidQueue)->Release(hidQueue); + hidQueue = 0; + } + + if (hidDevice != 0) + (*hidDevice)->close(hidDevice); + } + + std::string getName() { return name; } + + private: + + XBox360Controller() + { + for (int i=0; i<15; i++) + buttons[i] = 0; + + for (int i=0; i<6; i++) + axis[i] = 0; + } + + std::string getSerialNumber() + { + CFTypeRef value = IORegistryEntrySearchCFProperty(deviceHandle, kIOServicePlane, CFSTR("SerialNumber"), kCFAllocatorDefault, kIORegistryIterateRecursively); + std::string ret = std::string(CFStringGetCStringPtr((CFStringRef)value, kCFStringEncodingMacRoman)); + CFRelease(value); + + return ret; + } + + void updateLED(int ledIndex) + { + FFEFFESCAPE escape; + unsigned char c; + + if (forceFeedback ==0 ) + return; + + c = ledIndex; + escape.dwSize = sizeof(escape); + escape.dwCommand = 0x02; + escape.cbInBuffer = sizeof(c); + escape.lpvInBuffer = &c; + escape.cbOutBuffer = 0; + escape.lpvOutBuffer = NULL; + + if (FFDeviceEscape(forceFeedback, &escape) != FF_OK) + printf("ERROR: FFDeviceEscape.\n"); + } + + static void CallbackFunction(void* me, IOReturn result, void* refCon, void* sender) + { + ((XBox360Controller* )me)->handleCallback(result, refCon, sender); + } + + void handleCallback(IOReturn result, void* refCon, void* sender) + { + AbsoluteTime zeroTime={0,0}; + IOHIDEventStruct event; + bool found = false; + int i; + + if (sender != hidQueue) + return; + + while (result == kIOReturnSuccess) + { + result = (*hidQueue)->getNextEvent(hidQueue, &event, zeroTime, 0); + if (result != kIOReturnSuccess) + continue; + + // Check axis + for (i=0, found=FALSE; (i<6) && (!found); i++) + { + if (event.elementCookie == axis[i]) + { + int amount = 0; + if (i == 4 || i == 5) + amount = event.value * (32768/256) + 32768; + else + amount = 65536 - (event.value + 32768) - 1; + + // Clip. + if (amount > 65535) + amount = 65535; + if (amount < 0) + amount = 0; + + if (g_appleRemote.IsVerbose()) + printf("Axis %d %d.\n", i+1, amount); + CPacketBUTTON btn(i+1, "JS1:Wireless 360 Controller", BTN_AXIS | BTN_NO_REPEAT | BTN_USE_AMOUNT | BTN_QUEUE, amount); + g_appleRemote.SendPacket(btn); + + found = true; + } + } + + if (found) continue; + + // Check buttons + for (i=0, found=FALSE; (i<15) && (!found); i++) + { + if (event.elementCookie == buttons[i]) + { + if (g_appleRemote.IsVerbose()) + printf("Button: %d %d.\n", i+1, (int)event.value); + + if (i+1 == 11 && !g_appleRemote.IsProgramRunning("XBMC", 0) && + (g_appleRemote.GetServerAddress() == "127.0.0.1" || g_appleRemote.GetServerAddress() == "localhost") && + event.value) + { + g_appleRemote.LaunchApp(); + return; + } + + int flags = event.value ? BTN_DOWN : BTN_UP; + CPacketBUTTON btn(i+1, "JS1:Wireless 360 Controller", flags, 0); + g_appleRemote.SendPacket(btn); + found = true; + } + } + + if (found) continue; + + // Cookie wasn't for us? + } + } + + bool getVal(CFDictionaryRef element, const CFStringRef key, long& value) + { + CFTypeRef object = CFDictionaryGetValue(element, key); + + if ((object == NULL) || (CFGetTypeID(object) != CFNumberGetTypeID())) return false; + if (!CFNumberGetValue((CFNumberRef)object, kCFNumberLongType, &value)) return false; + + return true; + } + + IOHIDDeviceInterface122 **hidDevice; + FFDeviceObjectReference forceFeedback; + io_service_t deviceHandle; + + IOHIDQueueInterface **hidQueue; + IOHIDElementCookie axis[6]; + IOHIDElementCookie buttons[15]; + + std::string name; + int index; + bool wireless; +}; + + +class XBox360 +{ + public: + + XBox360() + { + } + + void start() + { + pthread_create(&_itsThread, NULL, Run, (void *)this); + } + + void join() + { + void* val = 0; + pthread_join(_itsThread, &val); + } + + protected: + + static void* Run(void* param) + { + ((XBox360* )param)->run(); + return 0; + } + + void run() + { + printf("XBOX360: Registering for notifications.\n"); + io_object_t object; + + // Register for wired notifications. + IONotificationPortRef notificationObject = IONotificationPortCreate(kIOMasterPortDefault); + IOServiceAddMatchingNotification(notificationObject, kIOFirstMatchNotification, IOServiceMatching(kIOUSBDeviceClassName), callbackHandleDevice, this, &_itsOnIteratorWired); + callbackHandleDevice(this, _itsOnIteratorWired); + + IOServiceAddMatchingNotification(notificationObject, kIOTerminatedNotification, IOServiceMatching(kIOUSBDeviceClassName), callbackHandleDevice, this, &_itsOffIteratorWired); + while ((object = IOIteratorNext(_itsOffIteratorWired)) != 0) + IOObjectRelease(object); + + // Wireless notifications. + IOServiceAddMatchingNotification(notificationObject, kIOFirstMatchNotification, IOServiceMatching("WirelessHIDDevice"), callbackHandleDevice, this, &_itsOnIteratorWireless); + callbackHandleDevice(this, _itsOnIteratorWireless); + + IOServiceAddMatchingNotification(notificationObject, kIOTerminatedNotification, IOServiceMatching("WirelessHIDDevice"), callbackHandleDevice, this, &_itsOffIteratorWireless); + while ((object = IOIteratorNext(_itsOffIteratorWireless)) != 0) + IOObjectRelease(object); + + // Add notifications to the run loop. + CFRunLoopSourceRef notificationRunLoopSource = IONotificationPortGetRunLoopSource(notificationObject); + CFRunLoopAddSource(CFRunLoopGetCurrent(), notificationRunLoopSource, kCFRunLoopDefaultMode); + + // Run. + CFRunLoopRun(); + printf("XBOX360: Exiting.\n"); + } + + private: + + // Callback for Xbox360 device notifications. + static void callbackHandleDevice(void* param, io_iterator_t iterator) + { + io_service_t object = 0; + bool update = false; + + while ((object = IOIteratorNext(iterator))!=0) + { + IOObjectRelease(object); + update = true; + } + + if (update) + ((XBox360* )param)->updateDeviceList(); + } + + void updateDeviceList() + { + io_iterator_t iterator; + io_object_t hidDevice; + + // Scrub and whack old items. + stopDevices(); + + for (std::list<XBox360Controller*>::iterator i = controllerList.begin(); i != controllerList.end(); ++i) + delete *i; + controllerList.clear(); + + // Add new items + CFMutableDictionaryRef hidDictionary = IOServiceMatching(kIOHIDDeviceKey); + IOReturn ioReturn = IOServiceGetMatchingServices(kIOMasterPortDefault, hidDictionary, &iterator); + + if ((ioReturn != kIOReturnSuccess) || (iterator==0)) + { + printf("No devices.\n"); + return; + } + + for (int count = 1; (hidDevice = IOIteratorNext(iterator)); ) + { + bool deviceWired = IOObjectConformsTo(hidDevice, "Xbox360ControllerClass"); + bool deviceWireless = IOObjectConformsTo(hidDevice, "WirelessHIDDevice"); + + if (deviceWired || deviceWireless) + { + XBox360Controller* controller = XBox360Controller::Create(hidDevice, count, deviceWireless); + if (controller) + { + // Add to list. + printf("Adding device: %s.\n", controller->getName().c_str()); + controllerList.push_back(controller); + count++; + } + } + else + { + IOObjectRelease(hidDevice); + } + } + + IOObjectRelease(iterator); + + // Make sure all devices are started. + startDevices(); + } + + void stopDevices() + { + for (std::list<XBox360Controller*>::iterator i = controllerList.begin(); i != controllerList.end(); ++i) + (*i)->stop(); + } + + void startDevices() + { + for (std::list<XBox360Controller*>::iterator i = controllerList.begin(); i != controllerList.end(); ++i) + (*i)->start(); + } + + std::list<XBox360Controller*> controllerList; + pthread_t _itsThread; + io_iterator_t _itsOnIteratorWired, _itsOffIteratorWired; + io_iterator_t _itsOnIteratorWireless, _itsOffIteratorWireless; +}; + +#endif diff --git a/tools/EventClients/Clients/AppleRemote/iremoted.c b/tools/EventClients/Clients/AppleRemote/iremoted.c new file mode 100644 index 0000000000..42b3258d03 --- /dev/null +++ b/tools/EventClients/Clients/AppleRemote/iremoted.c @@ -0,0 +1,518 @@ +/* + * iremoted.c + * Display events received from the Apple Infrared Remote. + * + * gcc -Wall -o iremoted iremoted.c -framework IOKit -framework Carbon + * + * Copyright (c) 2006-2008 Amit Singh. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define PROGNAME "iremoted" +#define PROGVERS "2.0" + +#include <stdio.h> +#include <getopt.h> +#include <unistd.h> +#include <stdlib.h> +#include <ctype.h> +#include <sys/errno.h> +#include <sysexits.h> +#include <mach/mach.h> +#include <mach/mach_error.h> +#include <assert.h> +#include <errno.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> +#include <sys/sysctl.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <mach-o/dyld.h> +#include <IOKit/IOKitLib.h> +#include <IOKit/IOCFPlugIn.h> +#include <IOKit/hid/IOHIDLib.h> +#include <IOKit/hid/IOHIDKeys.h> +#include <IOKit/hid/IOHIDUsageTables.h> + +#include <CoreFoundation/CoreFoundation.h> +#include <Carbon/Carbon.h> + +#include <iostream> +#include <fstream> +#include <map> +#include <string> +#include <iterator> +#include <sstream> +#include <set> + +#include "AppleRemote.h" +#include "XBox360.h" + +using namespace std; + +void ParseOptions(int argc, char** argv); +void ReadConfig(); + +static struct option long_options[] = { + { "help", no_argument, 0, 'h' }, + { "server", required_argument, 0, 's' }, + { "universal", no_argument, 0, 'u' }, + { "multiremote",no_argument, 0, 'm' }, + { "timeout", required_argument, 0, 't' }, + { "verbose", no_argument, 0, 'v' }, + { "externalConfig", no_argument, 0, 'x' }, + { "appPath", required_argument, 0, 'a' }, + { "appHome", required_argument, 0, 'z' }, + { 0, 0, 0, 0 }, +}; + +static const char *options = "hsumtvxaz"; + +IOHIDElementCookie buttonNextID = 0; +IOHIDElementCookie buttonPreviousID = 0; + +std::set<IOHIDElementCookie> g_registeredCookies; + +AppleRemote g_appleRemote; + +void usage(void); +inline void print_errmsg_if_io_err(int expr, char *msg); +inline void print_errmsg_if_err(int expr, char *msg); +void QueueCallbackFunction(void *target, IOReturn result, + void *refcon, void *sender); +bool addQueueCallbacks(IOHIDQueueInterface **hqi); +void processQueue(IOHIDDeviceInterface **hidDeviceInterface); +void doRun(IOHIDDeviceInterface **hidDeviceInterface); +void getHIDCookies(IOHIDDeviceInterface122 **handle); +void createHIDDeviceInterface(io_object_t hidDevice, + IOHIDDeviceInterface ***hdi); +void setupAndRun(void); + +void +usage(void) +{ + printf("%s (version %s)\n", PROGNAME, PROGVERS); + printf(" Sends Apple Remote events to XBMC.\n\n"); + printf("Usage: %s [OPTIONS...]\n\nOptions:\n", PROGNAME); + printf(" -h, --help print this help message and exit.\n"); + printf(" -s, --server <addr> send events to the specified IP.\n"); + printf(" -u, --universal runs in Universal Remote mode.\n"); + printf(" -t, --timeout <ms> timeout length for sequences (default: 500ms).\n"); + printf(" -a, --appPath path to XBMC.app (MenuPress launch support).\n"); + printf(" -z, --appHome path to XBMC.app/Content/Resources/XBMX \n"); + printf(" -v, --verbose prints lots of debugging information.\n"); +} + +inline void +print_errmsg_if_io_err(int expr, char *msg) +{ + IOReturn err = (expr); + + if (err != kIOReturnSuccess) { + fprintf(stderr, "*** %s - %s(%x, %d).\n", msg, mach_error_string(err), + err, err & 0xffffff); + fflush(stderr); + exit(EX_OSERR); + } +} + +inline void +print_errmsg_if_err(int expr, char *msg) +{ + if (expr) { + fprintf(stderr, "*** %s.\n", msg); + fflush(stderr); + exit(EX_OSERR); + } +} + +void +QueueCallbackFunction(void *target, IOReturn result, void *refcon, void *sender) +{ + HRESULT ret = kIOReturnSuccess; + AbsoluteTime zeroTime = {0,0}; + IOHIDQueueInterface **hqi; + IOHIDEventStruct event; + + std::set<int> events; + bool bKeyDown = false; + while (ret == kIOReturnSuccess) { + hqi = (IOHIDQueueInterface **)sender; + ret = (*hqi)->getNextEvent(hqi, &event, zeroTime, 0); + if (ret != kIOReturnSuccess) + continue; + + //printf("%d %d %d\n", (int)event.elementCookie, (int)event.value, (int)event.longValue); + + if (event.value > 0) + bKeyDown = true; + + events.insert((int)event.elementCookie); + } + + if (events.size() > 1) + { + std::set<int>::iterator iter = events.find( g_appleRemote.GetButtonEventTerminator() ); + if (iter != events.end()) + events.erase(iter); + } + + std::string strEvents; + std::set<int>::const_iterator iter = events.begin(); + while (iter != events.end()) + { + //printf("*iter = %d\n", *iter); + char strEvent[10]; + snprintf(strEvent, 10, "%d_", *iter); + strEvents += strEvent; + iter++; + } + + if (bKeyDown) + g_appleRemote.OnKeyDown(strEvents); + else + g_appleRemote.OnKeyUp(strEvents); +} + +bool +addQueueCallbacks(IOHIDQueueInterface **hqi) +{ + IOReturn ret; + CFRunLoopSourceRef eventSource; + IOHIDQueueInterface ***privateData; + + privateData = (IOHIDQueueInterface ***)malloc(sizeof(*privateData)); + *privateData = hqi; + + ret = (*hqi)->createAsyncEventSource(hqi, &eventSource); + if (ret != kIOReturnSuccess) + return false; + + ret = (*hqi)->setEventCallout(hqi, QueueCallbackFunction, + NULL, &privateData); + if (ret != kIOReturnSuccess) + return false; + + CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, + kCFRunLoopDefaultMode); + return true; +} + +void +processQueue(IOHIDDeviceInterface **hidDeviceInterface) +{ + HRESULT result; + IOHIDQueueInterface **queue; + + queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface); + if (!queue) { + fprintf(stderr, "Failed to allocate event queue.\n"); + return; + } + + (void)(*queue)->create(queue, 0, 99); + + std::set<IOHIDElementCookie>::const_iterator iter = g_registeredCookies.begin(); + while (iter != g_registeredCookies.end()) + { + (void)(*queue)->addElement(queue, *iter, 0); + iter++; + } + + addQueueCallbacks(queue); + + result = (*queue)->start(queue); + + CFRunLoopRun(); + + result = (*queue)->stop(queue); + + result = (*queue)->dispose(queue); + + (*queue)->Release(queue); +} + +void +doRun(IOHIDDeviceInterface **hidDeviceInterface) +{ + IOReturn ioReturnValue; + + ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, kIOHIDOptionsTypeSeizeDevice); + if (ioReturnValue == kIOReturnExclusiveAccess) { + printf("exclusive lock failed\n"); + } + + processQueue(hidDeviceInterface); + + if (ioReturnValue == KERN_SUCCESS) + ioReturnValue = (*hidDeviceInterface)->close(hidDeviceInterface); + (*hidDeviceInterface)->Release(hidDeviceInterface); +} + +void +getHIDCookies(IOHIDDeviceInterface122 **handle) +{ + IOHIDElementCookie cookie; + CFTypeRef object; + long number; + long usage; + long usagePage; + CFArrayRef elements; + CFDictionaryRef element; + IOReturn result; + + if (!handle || !(*handle)) + return ; + + result = (*handle)->copyMatchingElements(handle, NULL, &elements); + + if (result != kIOReturnSuccess) { + fprintf(stderr, "Failed to copy cookies.\n"); + exit(1); + } + + CFIndex i; + for (i = 0; i < CFArrayGetCount(elements); i++) { + element = (CFDictionaryRef)CFArrayGetValueAtIndex(elements, i); + object = (CFDictionaryGetValue(element, CFSTR(kIOHIDElementCookieKey))); + if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) + continue; + if(!CFNumberGetValue((CFNumberRef) object, kCFNumberLongType, &number)) + continue; + cookie = (IOHIDElementCookie)number; + object = CFDictionaryGetValue(element, CFSTR(kIOHIDElementUsageKey)); + if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) + continue; + if (!CFNumberGetValue((CFNumberRef)object, kCFNumberLongType, &number)) + continue; + usage = number; + object = CFDictionaryGetValue(element,CFSTR(kIOHIDElementUsagePageKey)); + if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) + continue; + if (!CFNumberGetValue((CFNumberRef)object, kCFNumberLongType, &number)) + continue; + usagePage = number; + if (usagePage == 0x06 && usage == 0x22 ) // event code "39". menu+select for 5 secs + g_registeredCookies.insert(cookie); + else if (usagePage == 0xff01 && usage == 0x23 ) // event code "35". long click - select + g_registeredCookies.insert(cookie); + else if (usagePage == kHIDPage_GenericDesktop && usage >= 0x80 && usage <= 0x8d ) // regular keys + g_registeredCookies.insert(cookie); + else if (usagePage == kHIDPage_Consumer && usage >= 0xB0 && usage <= 0xBF) + g_registeredCookies.insert(cookie); + else if (usagePage == kHIDPage_Consumer && usage == kHIDUsage_Csmr_Menu ) // event code "18". long "menu" + g_registeredCookies.insert(cookie); + } +} + +void +createHIDDeviceInterface(io_object_t hidDevice, IOHIDDeviceInterface ***hdi) +{ + io_name_t className; + IOCFPlugInInterface **plugInInterface = NULL; + HRESULT plugInResult = S_OK; + SInt32 score = 0; + IOReturn ioReturnValue = kIOReturnSuccess; + + ioReturnValue = IOObjectGetClass(hidDevice, className); + print_errmsg_if_io_err(ioReturnValue, "Failed to get class name."); + + ioReturnValue = IOCreatePlugInInterfaceForService( + hidDevice, + kIOHIDDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, + &plugInInterface, + &score); + + if (ioReturnValue != kIOReturnSuccess) + return; + + plugInResult = (*plugInInterface)->QueryInterface( + plugInInterface, + CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), + (LPVOID*)hdi); + print_errmsg_if_err(plugInResult != S_OK, + "Failed to create device interface.\n"); + + (*plugInInterface)->Release(plugInInterface); +} + +void +setupAndRun(void) +{ + CFMutableDictionaryRef hidMatchDictionary = NULL; + io_service_t hidService = (io_service_t)0; + io_object_t hidDevice = (io_object_t)0; + IOHIDDeviceInterface **hidDeviceInterface = NULL; + IOReturn ioReturnValue = kIOReturnSuccess; + + //hidMatchDictionary = IOServiceNameMatching("AppleIRController"); + hidMatchDictionary = IOServiceMatching("AppleIRController"); + hidService = IOServiceGetMatchingService(kIOMasterPortDefault, + hidMatchDictionary); + + if (!hidService) { + fprintf(stderr, "Apple Infrared Remote not found.\n"); + exit(1); + } + + hidDevice = (io_object_t)hidService; + + createHIDDeviceInterface(hidDevice, &hidDeviceInterface); + getHIDCookies((IOHIDDeviceInterface122 **)hidDeviceInterface); + ioReturnValue = IOObjectRelease(hidDevice); + print_errmsg_if_io_err(ioReturnValue, "Failed to release HID."); + + if (hidDeviceInterface == NULL) { + fprintf(stderr, "No HID.\n"); + exit(1); + } + + g_appleRemote.Initialize(); + doRun(hidDeviceInterface); + + if (ioReturnValue == KERN_SUCCESS) + ioReturnValue = (*hidDeviceInterface)->close(hidDeviceInterface); + + (*hidDeviceInterface)->Release(hidDeviceInterface); +} + +void Reconfigure(int nSignal) +{ + if (nSignal == SIGHUP) { + //fprintf(stderr, "Reconfigure\n"); + ReadConfig(); + } else { + exit(0); + } +} + +void ReadConfig() +{ + // Compute filename. + string strFile = getenv("HOME"); + strFile += "/Library/Application Support/XBMC/XBMCHelper.conf"; + + // Open file. + ifstream ifs(strFile.c_str()); + if (!ifs) + return; + + // Read file. + stringstream oss; + oss << ifs.rdbuf(); + + if (!ifs && !ifs.eof()) + return; + + // Tokenize. + string strData(oss.str()); + istringstream is(strData); + vector<string> args = vector<string>(istream_iterator<string>(is), istream_iterator<string>()); + + // Convert to char**. + int argc = args.size() + 1; + char** argv = new char*[argc + 1]; + int i = 0; + argv[i++] = "XBMCHelper"; + + for (vector<string>::iterator it = args.begin(); it != args.end(); ) + argv[i++] = (char* )(*it++).c_str(); + + argv[i] = 0; + + // Parse the arguments. + ParseOptions(argc, argv); + + delete[] argv; +} + +void ParseOptions(int argc, char** argv) +{ + int c, option_index = 0; + bool readExternal = false; + + while ((c = getopt_long(argc, argv, options, long_options, &option_index)) != -1) + { + switch (c) { + case 'h': + usage(); + exit(0); + break; + case 'v': + g_appleRemote.SetVerbose(true); + break; + case 's': + g_appleRemote.SetServerAddress(optarg); + break; + case 'u': + g_appleRemote.SetRemoteMode(REMOTE_UNIVERSAL); + break; + case 'm': + break; + case 't': + if (optarg) + g_appleRemote.SetMaxClickTimeout( atof(optarg) * 0.001 ); + break; + case 'a': + g_appleRemote.SetAppPath(optarg); + break; + case 'z': + g_appleRemote.SetAppHome(optarg); + break; + case 'x': + readExternal = true; + break; + default: + usage(); + exit(1); + break; + } + } + //reset getopts state + optreset = 1; + optind = 0; + + if (readExternal == true) + ReadConfig(); +} + +int +main (int argc, char **argv) +{ + ParseOptions(argc,argv); + + signal(SIGHUP, Reconfigure); + signal(SIGINT, Reconfigure); + signal(SIGTERM, Reconfigure); + + XBox360 xbox360; + xbox360.start(); + + setupAndRun(); + + xbox360.join(); + + return 0; +} diff --git a/tools/EventClients/Clients/J2ME Client/README b/tools/EventClients/Clients/J2ME Client/README new file mode 100644 index 0000000000..ee1426812c --- /dev/null +++ b/tools/EventClients/Clients/J2ME Client/README @@ -0,0 +1,23 @@ +1. Can I use it? +To use the J2ME client only CLDC 1.0 and MIDP 1.0 is needed. +The client will also need bluetooth and must be able to initialize the connection. + +2. Compiling the client +To compile the J2ME Application some libraries are needed: + *Suns Wireless Toolkit + *Java + *Ant +Before it can be compiled the link to Suns Wireless Toolkit are needed and are in build.xml +under <property name="WTK" value=""> where the value should be the path to the toolkit. +When compiling in ubuntu all that is needed are the following command +# ant +The final jar file will end up in build/release/ +For installations on some J2ME devices a jad file is needed and it needs the correct filesize of the JAR. This is not an +automated process and thus open the build/release/XBMCRemote.jad and alter it is requiered. The filesize is in bytes +and reflects that of build/release/XBMCRemote.jar + +3. Pre Usage +To use the EventClient you'll need to run the EventClient on a computer by: +# python XBMCJ2ME.py +It depends on Pybluez (http://org.csail.mit.edu/pybluez/) and is available in repository +# sudo apt-get install python-bluetooth diff --git a/tools/EventClients/Clients/J2ME Client/build.xml b/tools/EventClients/Clients/J2ME Client/build.xml new file mode 100644 index 0000000000..4e583b21bc --- /dev/null +++ b/tools/EventClients/Clients/J2ME Client/build.xml @@ -0,0 +1,31 @@ +<project name="XBMC Remote" default="make"> + <property name="WTK" value=""/> + <property name="OUTNAME" value="XBMCRemote"/> + <property name="MIDP10_LIB" value="${WTK}/lib/midpapi10.jar"/> + <property name="CLDC10_LIB" value="${WTK}/lib/cldcapi10.jar"/> + <property name="JSR082_LIB" value="${WTK}/lib/jsr082.jar"/> + <property name="ALL_LIB" value="${MIDP10_LIB}:${CLDC10_LIB}:${JSR082_LIB}"/> + + <target name="compile"> + <tstamp/> + <mkdir dir="build/compiled"/> + <javac destdir="build/compiled" srcdir="src" bootclasspath="${ALL_LIB}" target="1.1"/> + </target> + + <target name="preverify" depends="compile"> + <mkdir dir="build/preverified"/> + <exec executable="${WTK}/bin/preverify"> + <arg line="-classpath ${ALL_LIB}"/> + <arg line="-d build/preverified"/> + <arg line="build/compiled"/> + </exec> + </target> + + <target name="make" depends="preverify"> + <mkdir dir="build/release"/> + <jar basedir="build/preverified" jarfile="build/release/${OUTNAME}.jar" manifest="manifest.mf"> + <fileset dir="icons"/> + </jar> + <copy file="src/XBMCRemote.jad" tofile="build/release/${OUTNAME}.jad"/> + </target> +</project> diff --git a/tools/EventClients/Clients/J2ME Client/icons/icon.png b/tools/EventClients/Clients/J2ME Client/icons/icon.png Binary files differnew file mode 100644 index 0000000000..812237b3ac --- /dev/null +++ b/tools/EventClients/Clients/J2ME Client/icons/icon.png diff --git a/tools/EventClients/Clients/J2ME Client/j2me_remote.py b/tools/EventClients/Clients/J2ME Client/j2me_remote.py new file mode 100755 index 0000000000..5f78f83ffd --- /dev/null +++ b/tools/EventClients/Clients/J2ME Client/j2me_remote.py @@ -0,0 +1,175 @@ +#!/usr/bin/python +# +# XBMC Media Center +# J2ME Remote +# Copyright (c) 2008 topfs2 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +import sys +try: + from xbmc.xbmcclient import * + from xbmc.bt.bt import * + from xbmc.defs import * +except: + sys.path.append('../../lib/python') + from xbmcclient import * + from bt.bt import * + ICON_PATH = "../../icons/" + +from socket import * +import os +from threading import Thread + +host = "localhost" +port = 9777 +addr = (host, port) +sock = socket(AF_INET,SOCK_DGRAM) + +def send_key(key): + packet = PacketBUTTON(map_name="JS0:J2ME", code=key, repeat=0, queue=0) + packet.send(sock, addr) + +def send_message(caption, msg): + packet = PacketNOTIFICATION(caption, + msg, + ICON_PNG, + icon_file=ICON_PATH + "/phone.png") + + packet.send(sock, addr) + +def send_ImageToBluetooth(Image): + imageData = file( Image ).read() + print "Data len ", len( imageData ) + client_sock.send( format.uint32( len( imageData) ) ) + client_sock.sendall(imageData) + print "Sent file" + +class Ping(Thread): + def __init__ (self): + Thread.__init__(self) + def run(self): + import time + while 1: # ping the server every 19s + packet = PacketPING() + packet.send(sock, addr) + time.sleep (19) + +def main(): + import time + + # connect to localhost, port 9777 using a UDP socket + # this only needs to be done once. + # by default this is where XBMC will be listening for incoming + # connections. + server_sock=bt_create_rfcomm_socket() + server_sock.listen(2) + + portBT = server_sock.getsockname()[1] + + uuid = "ACDC" + bt_advertise(socket=server_sock, name="XBMC Remote", uuid=uuid) + + print "Waiting for connection on RFCOMM channel %d" % portBT + + packet = PacketHELO(devicename="J2ME Remote", + icon_type=ICON_PNG, + icon_file=ICON_PATH + "/phone.png") + packet.send(sock, addr) + Ping().start() + while(True): + try: + client_sock, client_info = server_sock.accept() + print "Accepted connection from ", client_info + client_addr, client_port = client_info + runFlag = True + firstRun = True + print "Accepted connection from ", client_addr + except KeyboardInterrupt: + packet = PacketLOG(LOGNOTICE, "J2ME: User interrupted") + packet.send(sock, addr) + bt_stop_advertising(socket = server_sock) + server_sock.close() + sys.exit(0) + try: + while runFlag: + if firstRun: + client_name = client_sock.recv(1024) + firstRun = False + send_message("J2ME: Phone Connected", client_name) + packet = PacketLOG(LOGNOTICE, "Phone Connected") + packet.send(sock, addr) + data = client_sock.recv(3) + if(data[0] == "0"): + if(data[1] == "0"): + runFlag = False + packet = PacketLOG(LOGNOTICE, "J2ME: Recieved disconnect command") + packet.send(sock, addr) + client_sock.close() + if(data[1] == "1"): + print "Shuting down server" + client_sock.close() + bt_stop_advertising(socket = server_sock) + server_sock.close() + packet = PacketBYE() + packet.send(sock, addr) + sys.exit(0) + + elif(data[0] == "2"): + print "<button id=\"%i\">" % ord( data[1] ) + send_key(ord( data[1] ) ) + else: + print "Unkown received data [%s]" % data + except IOError: + print "Lost connection too %s" % client_name + runFlag = False + send_message("Phone Disconnected", client_name) + pass + except KeyboardInterrupt: + packet = PacketLOG(LOGNOTICE, "J2ME: User interrupted") + packet.send(sock, addr) + client_sock.close() + bt_stop_advertising(socket = server_sock) + server_sock.close() + packet = PacketBYE() # PacketPING if you want to ping + packet.send(sock, addr) + sys.exit(0) + +def usage(): + print """ +Java Bluetooth Phone Remote Control Client for XBMC v0.1 + +Usage for xbmcphone.py + --address ADDRESS (default: localhost) + --port PORT (default: 9777) + --help (Brings up this message) +""" + +if __name__=="__main__": + try: + i = 0 + while (i < len(sys.argv)): + if (sys.argv[i] == "--address"): + host = sys.argv[i + 1] + elif (sys.argv[i] == "--port"): + port = sys.argv[i + 1] + elif (sys.argv[i] == "--help"): + usage() + sys.exit(0) + i = i + 1 + main() + except: + sys.exit(0) diff --git a/tools/EventClients/Clients/J2ME Client/manifest.mf b/tools/EventClients/Clients/J2ME Client/manifest.mf new file mode 100644 index 0000000000..49cddf5c28 --- /dev/null +++ b/tools/EventClients/Clients/J2ME Client/manifest.mf @@ -0,0 +1,7 @@ +MIDlet-1: MainScreen, , MainScreen +MIDlet-Vendor: team-xbmc +MIDlet-Name: XBMC Remote +MIDlet-Icon: icon.png +MIDlet-Version: 0.1 +MicroEdition-Configuration: CLDC-1.0 +MicroEdition-Profile: MIDP-1.0 diff --git a/tools/EventClients/Clients/J2ME Client/src/BluetoothCommunicator.java b/tools/EventClients/Clients/J2ME Client/src/BluetoothCommunicator.java new file mode 100644 index 0000000000..2db3f54dd2 --- /dev/null +++ b/tools/EventClients/Clients/J2ME Client/src/BluetoothCommunicator.java @@ -0,0 +1,228 @@ +/* +* XBMC Media Center +* UDP Event Server +* Copyright (c) 2008 topfs2 +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +import javax.microedition.io.Connector; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import javax.microedition.io.StreamConnection; +import java.lang.NullPointerException; +import java.util.Vector; +import javax.bluetooth.*; +import javax.bluetooth.LocalDevice; +import javax.microedition.lcdui.Form; +import javax.microedition.lcdui.Image; +import javax.microedition.lcdui.Display; +import java.lang.*; + +public class BluetoothCommunicator extends Thread +{ + private boolean Run; + private Form Log; + private BluetoothEvent recvEvent; + + protected static String Address = null; + private static StreamConnection Connection = null; + private static DataInputStream inputStream = null; + private static DataOutputStream outputStream = null; + + public BluetoothCommunicator(BluetoothEvent recvEvent, Form Log) + { + this.recvEvent = recvEvent; + this.Log = Log; + } + + public static boolean Connect(Form Log, String addr) + { + Log.append("Going to connect\n"); + try + { + Connection = (StreamConnection)Connector.open(addr); + + if (Connection == null) + { + Log.append("Connector.open returned null\n"); + return false; + } + else + { + Log.append("Connector.open returned connection\n"); + inputStream = Connection.openDataInputStream(); + outputStream = Connection.openDataOutputStream(); + Address = addr; + return true; + } + } + catch (Exception e) + { + Log.append("Exception" + e.getMessage() + "\n"); + try + { + if (Connection != null) + Connection.close(); + } + catch (Exception ee) + { + Log.append("Exception " + ee.getMessage() + "\n"); + } + Connection = null; + return false; + } + } + + public static boolean Handshake(Form Log, Display disp) + { + if (Connection == null) + return false; + try + { + LocalDevice localDevice = LocalDevice.getLocalDevice(); + Send(localDevice.getFriendlyName()); + + return true; + } + catch(NullPointerException NPE) + { + Log.append("Null " + NPE.getMessage()); + } + catch (IndexOutOfBoundsException IOBE) + { + Log.append("IndexOutOfBounds " + IOBE.getMessage()); + } + catch (IllegalArgumentException IAE) + { + Log.append("illegalArg " + IAE.getMessage()); + } + catch(Exception e) + { + Log.append("Exception in handshake " + e.getMessage()); + } + return false; + } + public void run() + { + Run = true; + while (Run) + { + try + { + if (inputStream.available() <= 0) + sleep(10); + else + { + Log.append("run:Ava " + inputStream.available() + "\n"); + byte []b = new byte[1]; + b[0] = (byte)255; // safe thing for now + inputStream.read(b, 0, 1); + byte[] recv = null; + switch (b[0]) + { + case BluetoothEvent.RECV_SYSTEM: + recv = new byte[]{ inputStream.readByte() }; + break; + case BluetoothEvent.RECV_COMMAND: + recv = new byte[]{ inputStream.readByte() }; + break; + case BluetoothEvent.RECV_IMAGE: + Log.append("Recv_Image\n"); + recv = RecvImage(Log); + break; + } + if (recv != null) + recvEvent.Recv(b[0], recv); + } + } + catch (Exception e) + { + Log.append("RecvException " + e.getMessage()); + } + } + } + public void close() + { + Run = false; + } + public static byte[] RecvImage(Form Log) + { + try + { + int length = inputStream.readInt(); + Log.append("readUnsignedShort: " + length + "\n"); + byte[] arrayToUse = new byte[length]; + inputStream.readFully(arrayToUse, 0, length); + return arrayToUse; + } + catch (Exception e ) + { + Log.append("Exception RecvImage " + e.getMessage()); + return null; + } + } + + public static int Recv(byte[] arrayToUse, Form Log ) + { + if (Connection == null) + return -1; + try + { + return inputStream.read(arrayToUse); + } + catch (Exception e) + { + Log.append("Exception in Recv " + e.getMessage()); + return -1; + } + } + + public static boolean Send(byte[] packet) + { + if (Connection == null) + return false; + try + { + outputStream.write(packet, 0, packet.length); + return true; + } + catch (Exception e) + { + return false; + } + } + + public static boolean Send(String packet) + { + return Send(packet.getBytes()); + } + + public static void Disconnect() + { + if (Connection == null) + return; + + try + { + byte[] packet = { '0', '0' }; + Send(packet); + Connection.close(); + } + catch (Exception e) + { } + + Connection = null; + } +} diff --git a/tools/EventClients/Clients/J2ME Client/src/BluetoothEvent.java b/tools/EventClients/Clients/J2ME Client/src/BluetoothEvent.java new file mode 100644 index 0000000000..6557dca2e0 --- /dev/null +++ b/tools/EventClients/Clients/J2ME Client/src/BluetoothEvent.java @@ -0,0 +1,27 @@ +/* +* XBMC Media Center +* UDP Event Server +* Copyright (c) 2008 topfs2 +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +public interface BluetoothEvent { + public final int RECV_SYSTEM = 0; + public final int RECV_COMMAND = 1; + public final int RECV_IMAGE = 2; + + // The header is a byte IRL. 0 - 255 and tells packet type + public void Recv(int Header, byte[] Payload); +} diff --git a/tools/EventClients/Clients/J2ME Client/src/BluetoothServiceDiscovery.java b/tools/EventClients/Clients/J2ME Client/src/BluetoothServiceDiscovery.java new file mode 100644 index 0000000000..3ff71f13f2 --- /dev/null +++ b/tools/EventClients/Clients/J2ME Client/src/BluetoothServiceDiscovery.java @@ -0,0 +1,126 @@ +/* +* XBMC Media Center +* UDP Event Server +* Copyright (c) 2008 topfs2 +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +import java.util.Vector; +import javax.bluetooth.DeviceClass; +import javax.bluetooth.DiscoveryAgent; +import javax.bluetooth.DiscoveryListener; +import javax.bluetooth.LocalDevice; +import javax.bluetooth.RemoteDevice; +import javax.bluetooth.ServiceRecord; +import javax.bluetooth.UUID; + +import javax.microedition.lcdui.*; + +public class BluetoothServiceDiscovery implements DiscoveryListener +{ + private static Object lock=new Object(); + private static Vector vecDevices=new Vector(); + private static String connectionURL=null; + + public static String Scan(Form Log, String ScanForUUID) throws Exception + { + BluetoothServiceDiscovery bluetoothServiceDiscovery=new BluetoothServiceDiscovery(); + //display local device address and name + LocalDevice localDevice = LocalDevice.getLocalDevice(); + //find devices + DiscoveryAgent agent = localDevice.getDiscoveryAgent(); + + agent.startInquiry(DiscoveryAgent.GIAC, bluetoothServiceDiscovery); + try + { + synchronized(lock) + { lock.wait(); } + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + Log.append("Device Inquiry Completed. \n"); + + int deviceCount=vecDevices.size(); + if(deviceCount <= 0) + { + Log.append("No Devices Found .\n"); + return null; + } + else + { + Log.append("Bluetooth Devices: \n"); + for (int i = 0; i <deviceCount; i++) + { + RemoteDevice remoteDevice=(RemoteDevice)vecDevices.elementAt(i); + String t = i + ": \"" + remoteDevice.getFriendlyName(true) + "\""; + Log.append(t + "\n"); + } + } + UUID[] uuidSet = new UUID[1]; + if (ScanForUUID != null) + uuidSet[0]=new UUID(ScanForUUID, true); + else + uuidSet[0]=new UUID("ACDC", true); + + for (int i = 0; i < vecDevices.capacity(); i++) + { + RemoteDevice remoteDevice=(RemoteDevice)vecDevices.elementAt(i); + String t = remoteDevice.getFriendlyName(true); + agent.searchServices(null,uuidSet,remoteDevice,bluetoothServiceDiscovery); + try + { + synchronized(lock) + { lock.wait(); } + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + if(connectionURL != null) + return connectionURL; + } + Log.append("Couldn't find any computer that were suitable\n"); + return null; + } + + public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) + { + if(!vecDevices.contains(btDevice)) + vecDevices.addElement(btDevice); + } + + public void servicesDiscovered(int transID, ServiceRecord[] servRecord) + { + if(servRecord!=null && servRecord.length>0){ + connectionURL=servRecord[0].getConnectionURL(0,false); + } + synchronized(lock) + { lock.notify(); } + } + + public void serviceSearchCompleted(int transID, int respCode) + { + synchronized(lock) + { lock.notify(); } + } + + public void inquiryCompleted(int discType) + { + synchronized(lock) + { lock.notify(); } + } +} diff --git a/tools/EventClients/Clients/J2ME Client/src/KeyCanvas.java b/tools/EventClients/Clients/J2ME Client/src/KeyCanvas.java new file mode 100644 index 0000000000..8c2153c0d6 --- /dev/null +++ b/tools/EventClients/Clients/J2ME Client/src/KeyCanvas.java @@ -0,0 +1,66 @@ +/* +* XBMC Media Center +* UDP Event Server +* Copyright (c) 2008 topfs2 +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +import javax.microedition.lcdui.*; + +public class KeyCanvas extends Canvas +{ + private KeyHandler m_SendKey = null; + private Image Cover = null; + private boolean SetCover = false; + + public KeyCanvas(KeyHandler sendKey) + { + m_SendKey = sendKey; + try + { +// Cover = Image.createImage("home.png"); + } + catch (Exception e) + { } + } + + public void SetCover(Image newCover) + { + if (newCover != null) + { + SetCover = true; + Cover = newCover; + } + } + + public void paint(Graphics g) + { + g.drawRect(0, 0, this.getWidth(), this.getHeight()); + if (Cover != null) + g.drawImage(Cover, 0, 0, g.TOP|g.LEFT); + } + + public void keyPressed(int keyCode) + { + if (m_SendKey != null) + m_SendKey.keyPressed(keyCode); + } + + public void keyRepeated(int keyCode) + { + if (m_SendKey != null) + m_SendKey.keyPressed(keyCode); + } +} diff --git a/tools/EventClients/Clients/J2ME Client/src/KeyHandler.java b/tools/EventClients/Clients/J2ME Client/src/KeyHandler.java new file mode 100644 index 0000000000..a6a5ac10d0 --- /dev/null +++ b/tools/EventClients/Clients/J2ME Client/src/KeyHandler.java @@ -0,0 +1,23 @@ +/* +* XBMC Media Center +* UDP Event Server +* Copyright (c) 2008 topfs2 +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +public interface KeyHandler +{ + public void keyPressed(int keyCode); +} diff --git a/tools/EventClients/Clients/J2ME Client/src/MainScreen.java b/tools/EventClients/Clients/J2ME Client/src/MainScreen.java new file mode 100644 index 0000000000..2e68c7d211 --- /dev/null +++ b/tools/EventClients/Clients/J2ME Client/src/MainScreen.java @@ -0,0 +1,271 @@ +/* +* XBMC Media Center +* UDP Event Server +* Copyright (c) 2008 topfs2 +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +import java.util.Vector; +import javax.bluetooth.DeviceClass; +import javax.bluetooth.DiscoveryAgent; +import javax.bluetooth.DiscoveryListener; +import javax.bluetooth.LocalDevice; +import javax.bluetooth.RemoteDevice; +import javax.bluetooth.ServiceRecord; +import javax.bluetooth.UUID; + +import javax.microedition.midlet.*; +import javax.microedition.lcdui.*; + +public class MainScreen extends MIDlet implements CommandListener, KeyHandler, BluetoothEvent +{ + private boolean LogActive = true; + private final int VersionMajor = 0; + private final int VersionMinor = 1; + private final String Version = (VersionMajor + "." + VersionMinor); + + private final Command CMD_OK = new Command("OK", Command.OK, 1); + private final Command CMD_CONNECT = new Command("Connect", Command.OK, 1); + private final Command CMD_DISCONNECT = new Command("Disconnect", Command.CANCEL, 1); + private final Command CMD_EXIT = new Command("Exit", Command.CANCEL, 1); + private final Command CMD_LOG = new Command("Log", Command.OK, 1); + private final Command CMD_SHOWREMOTE = new Command("Remote", Command.OK, 1); + private Display display; + private Form MainForm; + + private KeyCanvas keyHandler; + private BluetoothCommunicator RecvThread; + + private final int CONNECTED = 1; + private final int NOT_CONNECTED = -1; + private final int CONNECTING = 0; + + private int Connect = NOT_CONNECTED; + + public MainScreen() + { + display = Display.getDisplay(this); + keyHandler = new KeyCanvas(this); + keyHandler.addCommand(CMD_LOG); + keyHandler.addCommand(CMD_DISCONNECT); + keyHandler.setCommandListener(this); + } + + + public void Disconnect() + { + RecvThread.close(); + BluetoothCommunicator.Disconnect(); + MainForm.removeCommand(CMD_DISCONNECT); + MainForm.removeCommand(CMD_SHOWREMOTE); + MainForm.addCommand(CMD_CONNECT); + Connect = NOT_CONNECTED; + } + + public void Connect() + { + if (Connect == CONNECTED) + return; + Connect = CONNECTING; + MainForm = CreateForm("Connect log"); + display.setCurrent(MainForm); + try + { + String Connectable = BluetoothServiceDiscovery.Scan(MainForm, "ACDC"); + if (Connectable != null) + { + MainForm.append("\n" + Connectable); + display.setCurrent(MainForm); + if (BluetoothCommunicator.Connect(MainForm, Connectable)) + Connect = CONNECTED; + else + Connect = NOT_CONNECTED; + + display.setCurrent(MainForm); + BluetoothCommunicator.Handshake(MainForm, display); + RecvThread = new BluetoothCommunicator(this, MainForm); + + display.setCurrent(MainForm); + } + } + catch (Exception e) + { + MainForm.append("Error: Couldn't Search"); + Connect = NOT_CONNECTED; + display.setCurrent(MainForm); + return; + } + + if (Connect == CONNECTED) + { + MainForm.addCommand(CMD_SHOWREMOTE); + MainForm.addCommand(CMD_DISCONNECT); + MainForm.append("Connected to a server now we start the receive thread\n"); + RecvThread.start(); + } + else + { + MainForm.addCommand(CMD_CONNECT); + MainForm.addCommand(CMD_EXIT); + MainForm.append("Not connected to a server"); + } + setCurrent(); + } + + public void startApp() + { + int length = ((255 << 8) | (255)); + System.out.println("Len " + length); + + + MainForm = CreateForm("XBMC Remote"); + MainForm.append("Hi and welcome to the version " + Version + " of XBMC Remote.\nPressing Connect will have this remote connect to the first available Server"); + setCurrent(); + } + + private Form CreateForm(String Title) + { + Form rtnForm = new Form(Title); + if (Connect == CONNECTED) + rtnForm.addCommand(CMD_OK); + else if (Connect == NOT_CONNECTED) + rtnForm.addCommand(CMD_CONNECT); + + if (Connect == CONNECTED) + rtnForm.addCommand(CMD_DISCONNECT); + else if (Connect == NOT_CONNECTED) + rtnForm.addCommand(CMD_EXIT); + + rtnForm.setCommandListener(this); + return rtnForm; + } + + private void setCurrent() + { + setCurrent(false); + } + private void setCurrent(boolean forceLog) + { + if (forceLog) + display.setCurrent(MainForm); + else if (Connect == CONNECTED) + { + display.setCurrent(keyHandler); + keyHandler.repaint(); + } + else + { + display.setCurrent(MainForm); + } + } + + public void pauseApp() + { + } + + public void destroyApp(boolean unconditional) + { + if (Connect == CONNECTED) + { + BluetoothCommunicator.Disconnect(); + RecvThread.close(); + } + } + + public void commandAction(Command c, Displayable d) + { + if (c.equals(CMD_LOG)) + setCurrent(true); + else if (c.equals(CMD_SHOWREMOTE)) + { + display.setCurrent(keyHandler); + keyHandler.repaint(); + } + else if (d.equals(MainForm)) + { + if (c.equals(CMD_OK)) + { + byte[] Packet = {'1', '1'}; // 48 == '0' + BluetoothCommunicator.Send(Packet); + } + else if (c.equals(CMD_CONNECT)) + { + this.Connect(); + } + else if (c.equals(CMD_EXIT)) + System.out.println("QUIT"); + else if (c.equals(CMD_DISCONNECT)) + { + this.Disconnect(); + this.RecvThread.close(); + setCurrent(); + } + } + else if (d.equals(keyHandler)) + { + if (c.equals(CMD_OK)) + { + byte[] Packet = {'2', (byte)251 }; + BluetoothCommunicator.Send(Packet); + } + else if (c.equals(CMD_DISCONNECT)) + { + this.Disconnect(); + Connect = NOT_CONNECTED; + setCurrent(); + } + } + } + + public void keyPressed(int keyCode) + { + byte[] Packet = {'2', (byte)keyCode}; + if (keyCode >= Canvas.KEY_NUM0 && keyCode <= Canvas.KEY_NUM9) + Packet[1] = (byte)keyCode; + + else if (keyCode == Canvas.UP || keyCode == -1) + Packet[1] = 'U'; + else if (keyCode == Canvas.DOWN || keyCode == -2) + Packet[1] = 'D'; + else if (keyCode == Canvas.LEFT || keyCode == -3) + Packet[1] = 'L'; + else if (keyCode == Canvas.RIGHT || keyCode == -4) + Packet[1] = 'R'; + + else if (keyCode == Canvas.KEY_STAR) + Packet[1] = '*'; + else if (keyCode == Canvas.KEY_POUND) + Packet[1] = '#'; + + BluetoothCommunicator.Send(Packet); + System.out.println("got" + keyCode + " I'll send [" + Packet[0] + "," + Packet[1] + "]"); + } + public void Recv(int Header, byte[] Payload) + { + switch(Header) + { + case BluetoothEvent.RECV_COMMAND: + break; + case BluetoothEvent.RECV_SYSTEM: + if (Payload[0] == 0) + this.Disconnect(); + break; + case BluetoothEvent.RECV_IMAGE: + keyHandler.SetCover(Image.createImage(Payload, 0, Payload.length)); + break; + } + } +} diff --git a/tools/EventClients/Clients/J2ME Client/src/XBMCRemote.jad b/tools/EventClients/Clients/J2ME Client/src/XBMCRemote.jad new file mode 100644 index 0000000000..bd283d8903 --- /dev/null +++ b/tools/EventClients/Clients/J2ME Client/src/XBMCRemote.jad @@ -0,0 +1,9 @@ +MIDlet-1: MainScreen, , MainScreen
+MIDlet-Icon: icon.png
+MIDlet-Jar-Size: 12153
+MIDlet-Jar-URL: XBMC Remote.jar
+MIDlet-Name: XBMC Remote
+MIDlet-Vendor: team-xbmc
+MIDlet-Version: 0.1
+MicroEdition-Configuration: CLDC-1.0
+MicroEdition-Profile: MIDP-1.0 diff --git a/tools/EventClients/Clients/OSXRemote/Makefile.in b/tools/EventClients/Clients/OSXRemote/Makefile.in new file mode 100644 index 0000000000..4b66f568db --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/Makefile.in @@ -0,0 +1,18 @@ +#convenience makefile to allow headless osx build + +DEBUG_FLAGS=@DEBUG_FLAGS@ + +#check for debug build +ifneq (,$(findstring _DEBUG, $(DEBUG_FLAGS))) + CONFIGURATION=Debug +else + CONFIGURATION=Release +endif + +all: + xcodebuild -sdk macosx10.4 -configuration $(CONFIGURATION) + +clean: + xcodebuild clean -configuration $(CONFIGURATION) + +.PHONY: all clean diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/AppleRemote.h b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/AppleRemote.h new file mode 100644 index 0000000000..4bb2f0998d --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/AppleRemote.h @@ -0,0 +1,44 @@ +/***************************************************************************** + * RemoteControlWrapper.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED ‚ÄúAS IS‚Äù, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import <Cocoa/Cocoa.h> +#import "HIDRemoteControlDevice.h" + +/* Interacts with the Apple Remote Control HID device + The class is not thread safe + */ +@interface AppleRemote : HIDRemoteControlDevice { + int deviceID; +} + +//AppleRemote supports different device IDs (as long they are not paired) +//the deviceID of last sent button can be queried with - (int) deviceID +- (int) deviceID; + +//overwritten from baseclass to get notified of device changes +- (eCookieModifier) handleCookie: (long)f_cookie value:(int) f_value; +@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/AppleRemote.m b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/AppleRemote.m new file mode 100644 index 0000000000..152e755aa5 --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/AppleRemote.m @@ -0,0 +1,108 @@ +/***************************************************************************** + * RemoteControlWrapper.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import "AppleRemote.h" + +#import <mach/mach.h> +#import <mach/mach_error.h> +#import <IOKit/IOKitLib.h> +#import <IOKit/IOCFPlugIn.h> +#import <IOKit/hid/IOHIDKeys.h> + +const char* AppleRemoteDeviceName = "AppleIRController"; + +// the WWDC 07 Leopard Build is missing the constant +#ifndef NSAppKitVersionNumber10_4 +#define NSAppKitVersionNumber10_4 824 +#endif + +@implementation AppleRemote + ++ (const char*) remoteControlDeviceName { + return AppleRemoteDeviceName; +} + +- (void) setCookieMappingInDictionary: (NSMutableDictionary*) _cookieToButtonMapping { + if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_4) { + // 10.4.x Tiger + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlus] forKey:@"14_12_11_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMinus] forKey:@"14_13_11_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"14_7_6_14_7_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"14_8_6_14_8_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"14_9_6_14_9_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"14_10_6_14_10_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"14_6_4_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"14_6_3_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"14_6_14_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Hold] forKey:@"18_14_6_18_14_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"]; + } else { + // 10.5.x Leopard + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlus] forKey:@"31_29_28_19_18_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMinus] forKey:@"31_30_28_19_18_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"31_20_19_18_31_20_19_18_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"31_21_19_18_31_21_19_18_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"31_22_19_18_31_22_19_18_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"31_23_19_18_31_23_19_18_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"31_19_18_4_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"31_19_18_3_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"31_19_18_31_19_18_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Hold] forKey:@"35_31_19_18_35_31_19_18_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"39_"]; + } +} + +- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown { + if (pressedDown == NO && event == kRemoteButtonMenu_Hold) { + // There is no seperate event for pressed down on menu hold. We are simulating that event here + [super sendRemoteButtonEvent:event pressedDown:YES]; + } + + [super sendRemoteButtonEvent:event pressedDown:pressedDown]; + + if (pressedDown && (event == kRemoteButtonRight || event == kRemoteButtonLeft || event == kRemoteButtonPlay || event == kRemoteButtonMenu || event == kRemoteButtonPlay_Hold)) { + // There is no seperate event when the button is being released. We are simulating that event here + [super sendRemoteButtonEvent:event pressedDown:NO]; + } +} + +- (eCookieModifier) handleCookie: (long)f_cookie value:(int) f_value { + switch(f_cookie) + { + case 39: + deviceID = f_value; + return PASS_COOKIE; + default: + return [super handleCookie:f_cookie value:f_value]; + } +} + +- (int) deviceID { + return deviceID; +} +@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/GlobalKeyboardDevice.h b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/GlobalKeyboardDevice.h new file mode 100644 index 0000000000..bcb11b4245 --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/GlobalKeyboardDevice.h @@ -0,0 +1,49 @@ +/***************************************************************************** + * GlobalKeyboardDevice.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import <Cocoa/Cocoa.h> +#import <Carbon/Carbon.h> + +#import "RemoteControl.h" + +/* + This class registers for a number of global keyboard shortcuts to simulate a remote control + */ +@interface GlobalKeyboardDevice : RemoteControl { + + NSMutableDictionary* hotKeyRemoteEventMapping; + EventHandlerRef eventHandlerRef; + +} + +- (void) mapRemoteButton: (RemoteControlEventIdentifier) remoteButtonIdentifier defaultKeycode: (unsigned int) defaultKeycode defaultModifiers: (unsigned int) defaultModifiers; + +- (BOOL)registerHotKeyCode: (unsigned int) keycode modifiers: (unsigned int) modifiers remoteEventIdentifier: (RemoteControlEventIdentifier) identifier; + +static OSStatus hotKeyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void* refCon ); + +@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/GlobalKeyboardDevice.m b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/GlobalKeyboardDevice.m new file mode 100644 index 0000000000..f568d1f1a3 --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/GlobalKeyboardDevice.m @@ -0,0 +1,241 @@ +/***************************************************************************** + * GlobalKeyboardDevice.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + + +#import "GlobalKeyboardDevice.h" + +#define F1 122 +#define F2 120 +#define F3 99 +#define F4 118 +#define F5 96 +#define F6 97 +#define F7 98 + +/* + the following default keys are read and shall be used to change the keyboard mapping + + mac.remotecontrols.GlobalKeyboardDevice.plus_modifiers + mac.remotecontrols.GlobalKeyboardDevice.plus_keycode + mac.remotecontrols.GlobalKeyboardDevice.minus_modifiers + mac.remotecontrols.GlobalKeyboardDevice.minus_keycode + mac.remotecontrols.GlobalKeyboardDevice.play_modifiers + mac.remotecontrols.GlobalKeyboardDevice.play_keycode + mac.remotecontrols.GlobalKeyboardDevice.left_modifiers + mac.remotecontrols.GlobalKeyboardDevice.left_keycode + mac.remotecontrols.GlobalKeyboardDevice.right_modifiers + mac.remotecontrols.GlobalKeyboardDevice.right_keycode + mac.remotecontrols.GlobalKeyboardDevice.menu_modifiers + mac.remotecontrols.GlobalKeyboardDevice.menu_keycode + mac.remotecontrols.GlobalKeyboardDevice.playhold_modifiers + mac.remotecontrols.GlobalKeyboardDevice.playhold_keycode + */ + + +@implementation GlobalKeyboardDevice + +- (id) initWithDelegate: (id) _remoteControlDelegate { + if (self = [super initWithDelegate: _remoteControlDelegate]) { + hotKeyRemoteEventMapping = [[NSMutableDictionary alloc] init]; + + unsigned int modifiers = cmdKey + shiftKey /*+ optionKey*/ + controlKey; + + [self mapRemoteButton:kRemoteButtonPlus defaultKeycode:F1 defaultModifiers:modifiers]; + [self mapRemoteButton:kRemoteButtonMinus defaultKeycode:F2 defaultModifiers:modifiers]; + [self mapRemoteButton:kRemoteButtonPlay defaultKeycode:F3 defaultModifiers:modifiers]; + [self mapRemoteButton:kRemoteButtonLeft defaultKeycode:F4 defaultModifiers:modifiers]; + [self mapRemoteButton:kRemoteButtonRight defaultKeycode:F5 defaultModifiers:modifiers]; + [self mapRemoteButton:kRemoteButtonMenu defaultKeycode:F6 defaultModifiers:modifiers]; + [self mapRemoteButton:kRemoteButtonPlay_Hold defaultKeycode:F7 defaultModifiers:modifiers]; + } + return self; +} + +- (void) dealloc { + [hotKeyRemoteEventMapping release]; + [super dealloc]; +} + +- (void) mapRemoteButton: (RemoteControlEventIdentifier) remoteButtonIdentifier defaultKeycode: (unsigned int) defaultKeycode defaultModifiers: (unsigned int) defaultModifiers { + NSString* defaultsKey; + + switch(remoteButtonIdentifier) { + case kRemoteButtonPlus: + defaultsKey = @"plus"; + break; + case kRemoteButtonMinus: + defaultsKey = @"minus"; + break; + case kRemoteButtonMenu: + defaultsKey = @"menu"; + break; + case kRemoteButtonPlay: + defaultsKey = @"play"; + break; + case kRemoteButtonRight: + defaultsKey = @"right"; + break; + case kRemoteButtonLeft: + defaultsKey = @"left"; + break; + case kRemoteButtonPlay_Hold: + defaultsKey = @"playhold"; + break; + default: + NSLog(@"Unknown global keyboard defaults key for remote button identifier %d", remoteButtonIdentifier); + } + + NSNumber* modifiersCfg = [[NSUserDefaults standardUserDefaults] objectForKey: [NSString stringWithFormat: @"mac.remotecontrols.GlobalKeyboardDevice.%@_modifiers", defaultsKey]]; + NSNumber* keycodeCfg = [[NSUserDefaults standardUserDefaults] objectForKey: [NSString stringWithFormat: @"mac.remotecontrols.GlobalKeyboardDevice.%@_keycode", defaultsKey]]; + + unsigned int modifiers = defaultModifiers; + if (modifiersCfg) modifiers = [modifiersCfg unsignedIntValue]; + + unsigned int keycode = defaultKeycode; + if (keycodeCfg) keycode = [keycodeCfg unsignedIntValue]; + + [self registerHotKeyCode: keycode modifiers: modifiers remoteEventIdentifier: remoteButtonIdentifier]; +} + +- (void) setListeningToRemote: (BOOL) value { + if (value == [self isListeningToRemote]) return; + if (value) { + [self startListening: self]; + } else { + [self stopListening: self]; + } +} +- (BOOL) isListeningToRemote { + return (eventHandlerRef!=NULL); +} + +- (IBAction) startListening: (id) sender { + + if (eventHandlerRef) return; + + EventTypeSpec eventSpec[2] = { + { kEventClassKeyboard, kEventHotKeyPressed }, + { kEventClassKeyboard, kEventHotKeyReleased } + }; + + InstallEventHandler( GetEventDispatcherTarget(), + (EventHandlerProcPtr)hotKeyEventHandler, + 2, eventSpec, self, &eventHandlerRef); +} +- (IBAction) stopListening: (id) sender { + RemoveEventHandler(eventHandlerRef); + eventHandlerRef = NULL; +} + +- (BOOL) sendsEventForButtonIdentifier: (RemoteControlEventIdentifier) identifier { + NSEnumerator* values = [hotKeyRemoteEventMapping objectEnumerator]; + NSNumber* remoteIdentifier; + while( (remoteIdentifier = [values nextObject]) ) { + if ([remoteIdentifier unsignedIntValue] == identifier) return YES; + } + return NO; +} + ++ (const char*) remoteControlDeviceName { + return "Keyboard"; +} + +- (BOOL)registerHotKeyCode: (unsigned int) keycode modifiers: (unsigned int) modifiers remoteEventIdentifier: (RemoteControlEventIdentifier) identifier { + OSStatus err; + EventHotKeyID hotKeyID; + EventHotKeyRef carbonHotKey; + + hotKeyID.signature = 'PTHk'; + hotKeyID.id = (long)keycode; + + err = RegisterEventHotKey(keycode, modifiers, hotKeyID, GetEventDispatcherTarget(), (int)nil, &carbonHotKey ); + + if( err ) + return NO; + + [hotKeyRemoteEventMapping setObject: [NSNumber numberWithInt:identifier] forKey: [NSNumber numberWithUnsignedInt: hotKeyID.id]]; + + return YES; +} +/* + - (void)unregisterHotKey: (PTHotKey*)hotKey + { + OSStatus err; + EventHotKeyRef carbonHotKey; + NSValue* key; + + if( [[self allHotKeys] containsObject: hotKey] == NO ) + return; + + carbonHotKey = [self _carbonHotKeyForHotKey: hotKey]; + NSAssert( carbonHotKey != nil, @"" ); + + err = UnregisterEventHotKey( carbonHotKey ); + //Watch as we ignore 'err': + + key = [NSValue valueWithPointer: carbonHotKey]; + [mHotKeys removeObjectForKey: key]; + + [self _updateEventHandler]; + + //See that? Completely ignored + } + */ + +- (RemoteControlEventIdentifier) remoteControlEventIdentifierForID: (unsigned int) id { + NSNumber* remoteEventIdentifier = [hotKeyRemoteEventMapping objectForKey:[NSNumber numberWithUnsignedInt: id]]; + return [remoteEventIdentifier unsignedIntValue]; +} + +- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown { + [delegate sendRemoteButtonEvent: event pressedDown: pressedDown remoteControl:self]; +} + +static RemoteControlEventIdentifier lastEvent; + +static OSStatus hotKeyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void* userData ) +{ + GlobalKeyboardDevice* keyboardDevice = (GlobalKeyboardDevice*) userData; + EventHotKeyID hkCom; + GetEventParameter(inEvent,kEventParamDirectObject,typeEventHotKeyID,NULL,sizeof(hkCom),NULL,&hkCom); + + RemoteControlEventIdentifier identifier = [keyboardDevice remoteControlEventIdentifierForID:hkCom.id]; + if (identifier == 0) return noErr; + + BOOL pressedDown = YES; + if (identifier != lastEvent) { + lastEvent = identifier; + } else { + lastEvent = 0; + pressedDown = NO; + } + [keyboardDevice sendRemoteButtonEvent: identifier pressedDown: pressedDown]; + + return noErr; +} + +@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/HIDRemoteControlDevice.h b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/HIDRemoteControlDevice.h new file mode 100644 index 0000000000..1bd3dff60a --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/HIDRemoteControlDevice.h @@ -0,0 +1,77 @@ +/***************************************************************************** + * HIDRemoteControlDevice.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED ‚ÄúAS IS‚Äù, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import <Cocoa/Cocoa.h> +#import <Carbon/Carbon.h> +#import <IOKit/hid/IOHIDLib.h> + +#import "RemoteControl.h" + +typedef enum _eCookieModifier{ + DISCARD_COOKIE = 0, + PASS_COOKIE +} eCookieModifier; + +/* + Base class for HID based remote control devices + */ +@interface HIDRemoteControlDevice : RemoteControl { + IOHIDDeviceInterface** hidDeviceInterface; + IOHIDQueueInterface** queue; + NSMutableArray* allCookies; + NSMutableDictionary* cookieToButtonMapping; + CFRunLoopSourceRef eventSource; + + BOOL fixSecureEventInputBug; + BOOL openInExclusiveMode; + BOOL processesBacklog; + + int supportedButtonEvents; + + EventHandlerUPP appSwitchedHandlerUPP; + EventHandlerRef appSwitchedHandlerRef; +} + +// When your application needs to much time on the main thread when processing an event other events +// may already be received which are put on a backlog. As soon as your main thread +// has some spare time this backlog is processed and may flood your delegate with calls. +// Backlog processing is turned off by default. +- (BOOL) processesBacklog; +- (void) setProcessesBacklog: (BOOL) value; + +// methods that should be overwritten by subclasses +- (void) setCookieMappingInDictionary: (NSMutableDictionary*) cookieToButtonMapping; + +- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown; + +//overwrite this to be able to interact with cookie handling +//by default (f_cookie == 5) is discarded +- (eCookieModifier) handleCookie: (long)f_cookie value:(int) f_value; + ++ (BOOL) isRemoteAvailable; + +@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/HIDRemoteControlDevice.m b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/HIDRemoteControlDevice.m new file mode 100644 index 0000000000..5fa61c46b5 --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/HIDRemoteControlDevice.m @@ -0,0 +1,547 @@ +/***************************************************************************** + * HIDRemoteControlDevice.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import "HIDRemoteControlDevice.h" + +#import <mach/mach.h> +#import <mach/mach_error.h> +#import <IOKit/IOKitLib.h> +#import <IOKit/IOCFPlugIn.h> +#import <IOKit/hid/IOHIDKeys.h> +#import "XBMCDebugHelpers.h" + +@interface HIDRemoteControlDevice (PrivateMethods) +- (NSDictionary*) cookieToButtonMapping; +- (IOHIDQueueInterface**) queue; +- (IOHIDDeviceInterface**) hidDeviceInterface; +- (void) removeNotifcationObserver; +- (void) remoteControlAvailable:(NSNotification *)notification; +- (void) enableSecureInputFix; +- (void) disableSecureInputFix; +@end + +@interface HIDRemoteControlDevice (IOKitMethods) ++ (io_object_t) findRemoteDevice; +- (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice; +- (BOOL) initializeCookies; +- (BOOL) openDevice; +@end + +//handler to regrab device if openend in exclusive mode on app-switches +//installed in startListening and removed in stopListening +pascal OSStatus appSwitchedEventHandler(EventHandlerCallRef nextHandler, + EventRef switchEvent, + void* userData); + +@implementation HIDRemoteControlDevice + ++ (const char*) remoteControlDeviceName { + return ""; +} + ++ (BOOL) isRemoteAvailable { + io_object_t hidDevice = [self findRemoteDevice]; + if (hidDevice != 0) { + IOObjectRelease(hidDevice); + return YES; + } else { + return NO; + } +} + +- (id) initWithDelegate: (id) _remoteControlDelegate { + if ([[self class] isRemoteAvailable] == NO) return nil; + + if ( self = [super initWithDelegate: _remoteControlDelegate] ) { + openInExclusiveMode = YES; + queue = NULL; + hidDeviceInterface = NULL; + cookieToButtonMapping = [[NSMutableDictionary alloc] init]; + + [self setCookieMappingInDictionary: cookieToButtonMapping]; + + NSEnumerator* enumerator = [cookieToButtonMapping objectEnumerator]; + NSNumber* identifier; + supportedButtonEvents = 0; + while(identifier = [enumerator nextObject]) { + supportedButtonEvents |= [identifier intValue]; + } + + fixSecureEventInputBug = [[NSUserDefaults standardUserDefaults] boolForKey: @"remoteControlWrapperFixSecureEventInputBug"]; + appSwitchedHandlerUPP = NewEventHandlerUPP(appSwitchedEventHandler); + } + + return self; +} + +- (void) dealloc { + [self removeNotifcationObserver]; + [self stopListening:self]; + [cookieToButtonMapping release]; + [super dealloc]; +} + +- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown { + [delegate sendRemoteButtonEvent: event pressedDown: pressedDown remoteControl:self]; +} + +- (void) setCookieMappingInDictionary: (NSMutableDictionary*) cookieToButtonMapping { +} +- (int) remoteIdSwitchCookie { + return 0; +} + +- (BOOL) sendsEventForButtonIdentifier: (RemoteControlEventIdentifier) identifier { + return (supportedButtonEvents & identifier) == identifier; +} + +- (BOOL) isListeningToRemote { + return (hidDeviceInterface != NULL && allCookies != NULL && queue != NULL); +} + +- (void) setListeningToRemote: (BOOL) value { + if (value == NO) { + [self stopListening:self]; + } else { + [self startListening:self]; + } +} + +- (BOOL) isOpenInExclusiveMode { + return openInExclusiveMode; +} +- (void) setOpenInExclusiveMode: (BOOL) value { + openInExclusiveMode = value; +} + +- (BOOL) processesBacklog { + return processesBacklog; +} +- (void) setProcessesBacklog: (BOOL) value { + processesBacklog = value; +} + +//handler to regrab device if openend in exclusive mode on app-switches +//installed in startListening and removed in stopListening +pascal OSStatus appSwitchedEventHandler(EventHandlerCallRef nextHandler, + EventRef switchEvent, + void* userData) +{ + HIDRemoteControlDevice* p_this = (HIDRemoteControlDevice*)userData; + if (p_this->hidDeviceInterface) + { + if ((*(p_this->hidDeviceInterface))->close(p_this->hidDeviceInterface) != KERN_SUCCESS) + ELOG("failed closing Apple Remote device\n"); + + if ((*(p_this->hidDeviceInterface))->open(p_this->hidDeviceInterface, kIOHIDOptionsTypeSeizeDevice) != KERN_SUCCESS) + ELOG("failed opening Apple Remote device\n"); + } + //DLOG("Reopened Apple Remote in exclusive mode\n"); + return 0; +} + +- (IBAction) startListening: (id) sender { + if ([self isListeningToRemote]) return; + + // 4th July 2007 + // + // A security update in february of 2007 introduced an odd behavior. + // Whenever SecureEventInput is activated or deactivated the exclusive access + // to the remote control device is lost. This leads to very strange behavior where + // a press on the Menu button activates FrontRow while your app still gets the event. + // A great number of people have complained about this. + // + // Enabling the SecureEventInput and keeping it enabled does the trick. + // + // I'm pretty sure this is a kind of bug at Apple and I'm in contact with the responsible + // Apple Engineer. This solution is not a perfect one - I know. + // One of the side effects is that applications that listen for special global keyboard shortcuts (like Quicksilver) + // may get into problems as they no longer get the events. + // As there is no official Apple Remote API from Apple I also failed to open a technical incident on this. + // + // Note that there is a corresponding DisableSecureEventInput in the stopListening method below. + // + [self enableSecureInputFix]; + + [self removeNotifcationObserver]; + + io_object_t hidDevice = [[self class] findRemoteDevice]; + if (hidDevice == 0) return; + + if ([self createInterfaceForDevice:hidDevice] == NULL) { + goto error; + } + + if ([self initializeCookies]==NO) { + goto error; + } + + if ([self openDevice]==NO) { + goto error; + } + // be KVO friendly + [self willChangeValueForKey:@"listeningToRemote"]; + [self didChangeValueForKey:@"listeningToRemote"]; + goto cleanup; + +error: + [self stopListening:self]; + [self disableSecureInputFix]; + +cleanup: + IOObjectRelease(hidDevice); +} + +- (IBAction) stopListening: (id) sender { + if ([self isListeningToRemote]==NO) return; + + BOOL sendNotification = NO; + + if (eventSource != NULL) { + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode); + CFRelease(eventSource); + eventSource = NULL; + } + if (queue != NULL) { + (*queue)->stop(queue); + + //dispose of queue + (*queue)->dispose(queue); + + //release the queue we allocated + (*queue)->Release(queue); + + queue = NULL; + + sendNotification = YES; + } + + if (allCookies != nil) { + [allCookies autorelease]; + allCookies = nil; + } + + if (hidDeviceInterface != NULL) { + //close the device + (*hidDeviceInterface)->close(hidDeviceInterface); + + //release the interface + (*hidDeviceInterface)->Release(hidDeviceInterface); + + hidDeviceInterface = NULL; + } + [self disableSecureInputFix]; + + if ([self isOpenInExclusiveMode] && sendNotification) { + [[self class] sendFinishedNotifcationForAppIdentifier: nil]; + } + // be KVO friendly + [self willChangeValueForKey:@"listeningToRemote"]; + [self didChangeValueForKey:@"listeningToRemote"]; +} + +- (eCookieModifier) handleCookie: (long)f_cookie value:(int) f_value { + if(f_cookie == 5) + return DISCARD_COOKIE; + else + return PASS_COOKIE; +} + +@end + +@implementation HIDRemoteControlDevice (PrivateMethods) + +- (IOHIDQueueInterface**) queue { + return queue; +} + +- (IOHIDDeviceInterface**) hidDeviceInterface { + return hidDeviceInterface; +} + + +- (NSDictionary*) cookieToButtonMapping { + return cookieToButtonMapping; +} + +- (void) removeNotifcationObserver { + [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:FINISHED_USING_REMOTE_CONTROL_NOTIFICATION object:nil]; +} + +- (void) remoteControlAvailable:(NSNotification *)notification { + [self removeNotifcationObserver]; + [self startListening: self]; +} + +- (void) enableSecureInputFix { + if ([self isOpenInExclusiveMode]) { + //2 ways to fix exclusive mode, either the one mentioned above + if (fixSecureEventInputBug) + EnableSecureEventInput(); + else { + //or registering for all app-switch events and just reopening the device on each switch + const EventTypeSpec applicationEvents[] = { { kEventClassApplication, kEventAppFrontSwitched } }; + InstallApplicationEventHandler(appSwitchedHandlerUPP, + GetEventTypeCount(applicationEvents), applicationEvents, self, &appSwitchedHandlerRef); + } + } +} + +- (void) disableSecureInputFix{ + if ([self isOpenInExclusiveMode]){ + if(fixSecureEventInputBug) + DisableSecureEventInput(); + else{ + RemoveEventHandler(appSwitchedHandlerRef); + } + } +} + +@end + +/* Callback method for the device queue + Will be called for any event of any type (cookie) to which we subscribe + */ +static void QueueCallbackFunction(void* target, IOReturn result, void* refcon, void* sender) { + if (target < 0) { + NSLog(@"QueueCallbackFunction called with invalid target!"); + return; + } + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + HIDRemoteControlDevice* remote = (HIDRemoteControlDevice*)target; + IOHIDEventStruct event; + AbsoluteTime zeroTime = {0,0}; + NSMutableString* cookieString = [NSMutableString stringWithCapacity: 10]; + SInt32 sumOfValues = 0; + NSNumber* lastSubButtonId = nil; + while (result == kIOReturnSuccess) + { + result = (*[remote queue])->getNextEvent([remote queue], &event, zeroTime, 0); + if ( result != kIOReturnSuccess ) + continue; + + //printf("%d %d %d\n", event.elementCookie, event.value, event.longValue); + if([remote handleCookie:(long)event.elementCookie value:event.value] != DISCARD_COOKIE){ + sumOfValues+=event.value; + [cookieString appendString:[NSString stringWithFormat:@"%d_", event.elementCookie]]; + } + //check if this is a valid button + NSNumber* buttonId = [[remote cookieToButtonMapping] objectForKey: cookieString]; + if (buttonId != nil){ + if([remote processesBacklog]) { + //send the button + [remote sendRemoteButtonEvent: [buttonId intValue] pressedDown: (sumOfValues>0)]; + //reset + } else { + //store button for later use + lastSubButtonId = buttonId; + } + sumOfValues = 0; + cookieString = [NSMutableString stringWithCapacity: 10]; + } + } + if ([remote processesBacklog] == NO && lastSubButtonId != nil) { + // process the last event of the backlog and assume that the button is not pressed down any longer. + // The events in the backlog do not seem to be in order and therefore (in rare cases) the last event might be + // a button pressed down event while in reality the user has released it. + // NSLog(@"processing last event of backlog"); + [remote sendRemoteButtonEvent: [lastSubButtonId intValue] pressedDown: (sumOfValues>0)]; + } + if ([cookieString length] > 0) { + NSLog(@"Unknown button for cookiestring %@", cookieString); + } + [pool release]; +} + +@implementation HIDRemoteControlDevice (IOKitMethods) + +- (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice { + io_name_t className; + IOCFPlugInInterface** plugInInterface = NULL; + HRESULT plugInResult = S_OK; + SInt32 score = 0; + IOReturn ioReturnValue = kIOReturnSuccess; + + hidDeviceInterface = NULL; + + ioReturnValue = IOObjectGetClass(hidDevice, className); + + if (ioReturnValue != kIOReturnSuccess) { + NSLog(@"Error: Failed to get class name."); + return NULL; + } + + ioReturnValue = IOCreatePlugInInterfaceForService(hidDevice, + kIOHIDDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, + &plugInInterface, + &score); + if (ioReturnValue == kIOReturnSuccess) + { + //Call a method of the intermediate plug-in to create the device interface + plugInResult = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID) &hidDeviceInterface); + + if (plugInResult != S_OK) { + NSLog(@"Error: Couldn't create HID class device interface"); + } + // Release + if (plugInInterface) (*plugInInterface)->Release(plugInInterface); + } + return hidDeviceInterface; +} + +- (BOOL) initializeCookies { + IOHIDDeviceInterface122** handle = (IOHIDDeviceInterface122**)hidDeviceInterface; + IOHIDElementCookie cookie; + long usage; + long usagePage; + id object; + NSArray* elements = nil; + NSDictionary* element; + IOReturn success; + + if (!handle || !(*handle)) return NO; + + // Copy all elements, since we're grabbing most of the elements + // for this device anyway, and thus, it's faster to iterate them + // ourselves. When grabbing only one or two elements, a matching + // dictionary should be passed in here instead of NULL. + success = (*handle)->copyMatchingElements(handle, NULL, (CFArrayRef*)&elements); + + if (success == kIOReturnSuccess) { + + [elements autorelease]; + /* + cookies = calloc(NUMBER_OF_APPLE_REMOTE_ACTIONS, sizeof(IOHIDElementCookie)); + memset(cookies, 0, sizeof(IOHIDElementCookie) * NUMBER_OF_APPLE_REMOTE_ACTIONS); + */ + allCookies = [[NSMutableArray alloc] init]; + + NSEnumerator *elementsEnumerator = [elements objectEnumerator]; + + while (element = [elementsEnumerator nextObject]) { + //Get cookie + object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementCookieKey) ]; + if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; + if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) continue; + cookie = (IOHIDElementCookie) [object longValue]; + + //Get usage + object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsageKey) ]; + if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; + usage = [object longValue]; + + //Get usage page + object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsagePageKey) ]; + if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; + usagePage = [object longValue]; + + [allCookies addObject: [NSNumber numberWithInt:(int)cookie]]; + } + } else { + return NO; + } + + return YES; +} + +- (BOOL) openDevice { + HRESULT result; + + IOHIDOptionsType openMode = kIOHIDOptionsTypeNone; + if ([self isOpenInExclusiveMode]) openMode = kIOHIDOptionsTypeSeizeDevice; + IOReturn ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, openMode); + + if (ioReturnValue == KERN_SUCCESS) { + queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface); + if (queue) { + result = (*queue)->create(queue, 0, 12); //depth: maximum number of elements in queue before oldest elements in queue begin to be lost. + + IOHIDElementCookie cookie; + NSEnumerator *allCookiesEnumerator = [allCookies objectEnumerator]; + + while (cookie = (IOHIDElementCookie)[[allCookiesEnumerator nextObject] intValue]) { + (*queue)->addElement(queue, cookie, 0); + } + + // add callback for async events + ioReturnValue = (*queue)->createAsyncEventSource(queue, &eventSource); + if (ioReturnValue == KERN_SUCCESS) { + ioReturnValue = (*queue)->setEventCallout(queue,QueueCallbackFunction, self, NULL); + if (ioReturnValue == KERN_SUCCESS) { + CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode); + + //start data delivery to queue + (*queue)->start(queue); + return YES; + } else { + NSLog(@"Error when setting event callback"); + } + } else { + NSLog(@"Error when creating async event source"); + } + } else { + NSLog(@"Error when opening device"); + } + } else if (ioReturnValue == kIOReturnExclusiveAccess) { + // the device is used exclusive by another application + + // 1. we register for the FINISHED_USING_REMOTE_CONTROL_NOTIFICATION notification + [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(remoteControlAvailable:) name:FINISHED_USING_REMOTE_CONTROL_NOTIFICATION object:nil]; + + // 2. send a distributed notification that we wanted to use the remote control + [[self class] sendRequestForRemoteControlNotification]; + } + return NO; +} + ++ (io_object_t) findRemoteDevice { + CFMutableDictionaryRef hidMatchDictionary = NULL; + IOReturn ioReturnValue = kIOReturnSuccess; + io_iterator_t hidObjectIterator = 0; + io_object_t hidDevice = 0; + + // Set up a matching dictionary to search the I/O Registry by class + // name for all HID class devices + hidMatchDictionary = IOServiceMatching([self remoteControlDeviceName]); + + // Now search I/O Registry for matching devices. + ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator); + + if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0)) { + hidDevice = IOIteratorNext(hidObjectIterator); + } + + // release the iterator + IOObjectRelease(hidObjectIterator); + + return hidDevice; +} + +@end + diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/IRKeyboardEmu.h b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/IRKeyboardEmu.h new file mode 100644 index 0000000000..8a842da8f3 --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/IRKeyboardEmu.h @@ -0,0 +1,16 @@ +// +// IRKeyboardEmu.h +// XBMCHelper +// +// Created by Stephan Diederich on 14.04.09. +// Copyright 2009 __MyCompanyName__. All rights reserved. +// + +#import <Cocoa/Cocoa.h> +#import "AppleRemote.h" + +@interface IRKeyboardEmu : AppleRemote { + +} + +@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/IRKeyboardEmu.m b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/IRKeyboardEmu.m new file mode 100644 index 0000000000..5350e16c0c --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/IRKeyboardEmu.m @@ -0,0 +1,26 @@ +// +// IRKeyboardEmu.m +// XBMCHelper +// +// Created by Stephan Diederich on 14.04.09. +// Copyright 2009 __MyCompanyName__. All rights reserved. +// + +#import "IRKeyboardEmu.h" + + +@implementation IRKeyboardEmu + ++ (const char*) remoteControlDeviceName { + return "IRKeyboardEmu"; +} + +- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown { + if (pressedDown == NO && event == kRemoteButtonMenu_Hold) { + // There is no seperate event for pressed down on menu hold. We are simulating that event here + [super sendRemoteButtonEvent:event pressedDown:YES]; + } + + [super sendRemoteButtonEvent:event pressedDown:pressedDown]; +} +@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/KeyspanFrontRowControl.h b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/KeyspanFrontRowControl.h new file mode 100644 index 0000000000..3a8224154f --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/KeyspanFrontRowControl.h @@ -0,0 +1,39 @@ +/***************************************************************************** + * KeyspanFrontRowControl.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + + +#import <Cocoa/Cocoa.h> +#import "HIDRemoteControlDevice.h" + +/* Interacts with the Keyspan FrontRow Remote Control HID device + The class is not thread safe + */ +@interface KeyspanFrontRowControl : HIDRemoteControlDevice { + +} + +@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/KeyspanFrontRowControl.m b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/KeyspanFrontRowControl.m new file mode 100644 index 0000000000..7dbcc853ec --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/KeyspanFrontRowControl.m @@ -0,0 +1,87 @@ +/***************************************************************************** + * KeyspanFrontRowControl.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import "KeyspanFrontRowControl.h" +#import <mach/mach.h> +#import <mach/mach_error.h> +#import <IOKit/IOKitLib.h> +#import <IOKit/IOCFPlugIn.h> +#import <IOKit/hid/IOHIDKeys.h> + +@implementation KeyspanFrontRowControl + +- (void) setCookieMappingInDictionary: (NSMutableDictionary*) _cookieToButtonMapping { + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlus] forKey:@"11_18_99_10_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMinus] forKey:@"11_18_98_10_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"11_18_58_10_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"11_18_61_10_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"11_18_96_10_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"11_18_97_10_"]; + /* hold events are not being send by this device + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"14_6_4_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"14_6_3_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"14_6_14_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Sleep] forKey:@"18_14_6_18_14_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"]; + */ +} + ++ (io_object_t) findRemoteDevice { + CFMutableDictionaryRef hidMatchDictionary = NULL; + IOReturn ioReturnValue = kIOReturnSuccess; + io_iterator_t hidObjectIterator = 0; + io_object_t hidDevice = 0; + SInt32 idVendor = 1741; + SInt32 idProduct = 0x420; + + // Set up a matching dictionary to search the I/O Registry by class + // name for all HID class devices + hidMatchDictionary = IOServiceMatching(kIOHIDDeviceKey); + + CFNumberRef numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &idVendor); + CFDictionaryAddValue(hidMatchDictionary, CFSTR(kIOHIDVendorIDKey), numberRef); + CFRelease(numberRef); + + numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &idProduct); + CFDictionaryAddValue(hidMatchDictionary, CFSTR(kIOHIDProductIDKey), numberRef); + CFRelease(numberRef); + + // Now search I/O Registry for matching devices. + ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator); + + if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0)) { + hidDevice = IOIteratorNext(hidObjectIterator); + } + + // release the iterator + IOObjectRelease(hidObjectIterator); + + return hidDevice; + +} + +@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/MultiClickRemoteBehavior.h b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/MultiClickRemoteBehavior.h new file mode 100644 index 0000000000..8b288604f7 --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/MultiClickRemoteBehavior.h @@ -0,0 +1,90 @@ +/***************************************************************************** + * MultiClickRemoteBehavior.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + + +#import <Cocoa/Cocoa.h> +#import "RemoteControl.h" + +/** + A behavior that adds multiclick and hold events on top of a device. + Events are generated and send to a delegate + */ +@interface MultiClickRemoteBehavior : NSObject { + id delegate; + + // state for simulating plus/minus hold + BOOL simulateHoldEvents; + BOOL lastEventSimulatedHold; + RemoteControlEventIdentifier lastHoldEvent; + NSTimeInterval lastHoldEventTime; + + // state for multi click + unsigned int clickCountEnabledButtons; + NSTimeInterval maxClickTimeDifference; + NSTimeInterval lastClickCountEventTime; + RemoteControlEventIdentifier lastClickCountEvent; + unsigned int eventClickCount; +} + +- (id) init; + +// Delegates are not retained +- (void) setDelegate: (id) delegate; +- (id) delegate; + +// Simulating hold events does deactivate sending of individual requests for pressed down/released. +// Instead special hold events are being triggered when the user is pressing and holding a button for a small period. +// Simulation is activated only for those buttons and remote control that do not have a seperate event already +- (BOOL) simulateHoldEvent; +- (void) setSimulateHoldEvent: (BOOL) value; + +// click counting makes it possible to recognize if the user has pressed a button repeatedly +// click counting does delay each event as it has to wait if there is another event (second click) +// therefore there is a slight time difference (maximumClickCountTimeDifference) between a single click +// of the user and the call of your delegate method +// click counting can be enabled individually for specific buttons. Use the property clickCountEnableButtons to +// set the buttons for which click counting shall be enabled +- (BOOL) clickCountingEnabled; +- (void) setClickCountingEnabled: (BOOL) value; + +- (unsigned int) clickCountEnabledButtons; +- (void) setClickCountEnabledButtons: (unsigned int)value; + +// the maximum time difference till which clicks are recognized as multi clicks +- (NSTimeInterval) maximumClickCountTimeDifference; +- (void) setMaximumClickCountTimeDifference: (NSTimeInterval) timeDiff; + +@end + +/* + * Method definitions for the delegate of the MultiClickRemoteBehavior class + */ +@interface NSObject(MultiClickRemoteBehaviorDelegate) + +- (void) remoteButton: (RemoteControlEventIdentifier)buttonIdentifier pressedDown: (BOOL) pressedDown clickCount: (unsigned int) count; + +@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/MultiClickRemoteBehavior.m b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/MultiClickRemoteBehavior.m new file mode 100644 index 0000000000..4b40fb3df8 --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/MultiClickRemoteBehavior.m @@ -0,0 +1,210 @@ +/***************************************************************************** + * MultiClickRemoteBehavior.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import "MultiClickRemoteBehavior.h" + +const NSTimeInterval DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE=0.35; +const NSTimeInterval HOLD_RECOGNITION_TIME_INTERVAL=0.4; + +@implementation MultiClickRemoteBehavior + +- (id) init { + if (self = [super init]) { + maxClickTimeDifference = DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE; + } + return self; +} + +// Delegates are not retained! +// http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CommunicatingWithObjects/chapter_6_section_4.html +// Delegating objects do not (and should not) retain their delegates. +// However, clients of delegating objects (applications, usually) are responsible for ensuring that their delegates are around +// to receive delegation messages. To do this, they may have to retain the delegate. +- (void) setDelegate: (id) _delegate { + if (_delegate && [_delegate respondsToSelector:@selector(remoteButton:pressedDown:clickCount:)]==NO) return; + + delegate = _delegate; +} +- (id) delegate { + return delegate; +} + +- (BOOL) simulateHoldEvent { + return simulateHoldEvents; +} +- (void) setSimulateHoldEvent: (BOOL) value { + simulateHoldEvents = value; +} + +- (BOOL) simulatesHoldForButtonIdentifier: (RemoteControlEventIdentifier) identifier remoteControl: (RemoteControl*) remoteControl { + // we do that check only for the normal button identifiers as we would check for hold support for hold events instead + if (identifier > (1 << EVENT_TO_HOLD_EVENT_OFFSET)) return NO; + + return [self simulateHoldEvent] && [remoteControl sendsEventForButtonIdentifier: (identifier << EVENT_TO_HOLD_EVENT_OFFSET)]==NO; +} + +- (BOOL) clickCountingEnabled { + return clickCountEnabledButtons != 0; +} +- (void) setClickCountingEnabled: (BOOL) value { + if (value) { + [self setClickCountEnabledButtons: kRemoteButtonPlus | kRemoteButtonMinus | kRemoteButtonPlay | kRemoteButtonLeft | kRemoteButtonRight | kRemoteButtonMenu]; + } else { + [self setClickCountEnabledButtons: 0]; + } +} + +- (unsigned int) clickCountEnabledButtons { + return clickCountEnabledButtons; +} +- (void) setClickCountEnabledButtons: (unsigned int)value { + clickCountEnabledButtons = value; +} + +- (NSTimeInterval) maximumClickCountTimeDifference { + return maxClickTimeDifference; +} +- (void) setMaximumClickCountTimeDifference: (NSTimeInterval) timeDiff { + maxClickTimeDifference = timeDiff; +} + +- (void) sendSimulatedHoldEvent: (id) time { + BOOL startSimulateHold = NO; + RemoteControlEventIdentifier event = lastHoldEvent; + @synchronized(self) { + startSimulateHold = (lastHoldEvent>0 && lastHoldEventTime == [time doubleValue]); + } + if (startSimulateHold) { + lastEventSimulatedHold = YES; + event = (event << EVENT_TO_HOLD_EVENT_OFFSET); + [delegate remoteButton:event pressedDown: YES clickCount: 1]; + } +} + +- (void) executeClickCountEvent: (NSArray*) values { + RemoteControlEventIdentifier event = [[values objectAtIndex: 0] unsignedIntValue]; + NSTimeInterval eventTimePoint = [[values objectAtIndex: 1] doubleValue]; + + BOOL finishedClicking = NO; + int finalClickCount = eventClickCount; + + @synchronized(self) { + finishedClicking = (event != lastClickCountEvent || eventTimePoint == lastClickCountEventTime); + if (finishedClicking) { + eventClickCount = 0; + lastClickCountEvent = 0; + lastClickCountEventTime = 0; + } + } + + if (finishedClicking) { + [delegate remoteButton:event pressedDown: YES clickCount:finalClickCount]; + // trigger a button release event, too + [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow:0.1]]; + [delegate remoteButton:event pressedDown: NO clickCount:finalClickCount]; + } +} + +- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown remoteControl: (RemoteControl*) remoteControl { + if (!delegate) return; + + BOOL clickCountingForEvent = ([self clickCountEnabledButtons] & event) == event; + + if ([self simulatesHoldForButtonIdentifier: event remoteControl: remoteControl] && lastClickCountEvent==0) { + if (pressedDown) { + // wait to see if it is a hold + lastHoldEvent = event; + lastHoldEventTime = [NSDate timeIntervalSinceReferenceDate]; + [self performSelector:@selector(sendSimulatedHoldEvent:) + withObject:[NSNumber numberWithDouble:lastHoldEventTime] + afterDelay:HOLD_RECOGNITION_TIME_INTERVAL]; + return; + } else { + if (lastEventSimulatedHold) { + // it was a hold + // send an event for "hold release" + event = (event << EVENT_TO_HOLD_EVENT_OFFSET); + lastHoldEvent = 0; + lastEventSimulatedHold = NO; + + [delegate remoteButton:event pressedDown: pressedDown clickCount:1]; + return; + } else { + RemoteControlEventIdentifier previousEvent = lastHoldEvent; + @synchronized(self) { + lastHoldEvent = 0; + } + + // in case click counting is enabled we have to setup the state for that, too + if (clickCountingForEvent) { + lastClickCountEvent = previousEvent; + lastClickCountEventTime = lastHoldEventTime; + NSNumber* eventNumber; + NSNumber* timeNumber; + eventClickCount = 1; + timeNumber = [NSNumber numberWithDouble:lastClickCountEventTime]; + eventNumber= [NSNumber numberWithUnsignedInt:previousEvent]; + NSTimeInterval diffTime = maxClickTimeDifference-([NSDate timeIntervalSinceReferenceDate]-lastHoldEventTime); + [self performSelector: @selector(executeClickCountEvent:) + withObject: [NSArray arrayWithObjects:eventNumber, timeNumber, nil] + afterDelay: diffTime]; + // we do not return here because we are still in the press-release event + // that will be consumed below + } else { + // trigger the pressed down event that we consumed first + [delegate remoteButton:event pressedDown: YES clickCount:1]; + } + } + } + } + + if (clickCountingForEvent) { + if (pressedDown == NO) return; + + NSNumber* eventNumber; + NSNumber* timeNumber; + @synchronized(self) { + lastClickCountEventTime = [NSDate timeIntervalSinceReferenceDate]; + if (lastClickCountEvent == event) { + eventClickCount = eventClickCount + 1; + } else { + eventClickCount = 1; + } + lastClickCountEvent = event; + timeNumber = [NSNumber numberWithDouble:lastClickCountEventTime]; + eventNumber= [NSNumber numberWithUnsignedInt:event]; + } + [self performSelector: @selector(executeClickCountEvent:) + withObject: [NSArray arrayWithObjects:eventNumber, timeNumber, nil] + afterDelay: maxClickTimeDifference]; + } else { + [delegate remoteButton:event pressedDown: pressedDown clickCount:1]; + } + +} + +@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControl.h b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControl.h new file mode 100644 index 0000000000..34e9d5e19d --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControl.h @@ -0,0 +1,102 @@ +/***************************************************************************** + * RemoteControl.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import <Cocoa/Cocoa.h> + +// notifaction names that are being used to signal that an application wants to +// have access to the remote control device or if the application has finished +// using the remote control device +extern NSString* REQUEST_FOR_REMOTE_CONTROL_NOTIFCATION; +extern NSString* FINISHED_USING_REMOTE_CONTROL_NOTIFICATION; + +// keys used in user objects for distributed notifications +extern NSString* kRemoteControlDeviceName; +extern NSString* kApplicationIdentifier; +extern NSString* kTargetApplicationIdentifier; + +// we have a 6 bit offset to make a hold event out of a normal event +#define EVENT_TO_HOLD_EVENT_OFFSET 6 + +@class RemoteControl; + +typedef enum _RemoteControlEventIdentifier { + // normal events + kRemoteButtonPlus =1<<1, + kRemoteButtonMinus =1<<2, + kRemoteButtonMenu =1<<3, + kRemoteButtonPlay =1<<4, + kRemoteButtonRight =1<<5, + kRemoteButtonLeft =1<<6, + + // hold events + kRemoteButtonPlus_Hold =1<<7, + kRemoteButtonMinus_Hold =1<<8, + kRemoteButtonMenu_Hold =1<<9, + kRemoteButtonPlay_Hold =1<<10, + kRemoteButtonRight_Hold =1<<11, + kRemoteButtonLeft_Hold =1<<12, + + // special events (not supported by all devices) + kRemoteControl_Switched =1<<13, +} RemoteControlEventIdentifier; + +@interface NSObject(RemoteControlDelegate) + +- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown remoteControl: (RemoteControl*) remoteControl; + +@end + +/* + Base Interface for Remote Control devices + */ +@interface RemoteControl : NSObject { + id delegate; +} + +// returns nil if the remote control device is not available +- (id) initWithDelegate: (id) remoteControlDelegate; + +- (void) setListeningToRemote: (BOOL) value; +- (BOOL) isListeningToRemote; + +- (BOOL) isOpenInExclusiveMode; +- (void) setOpenInExclusiveMode: (BOOL) value; + +- (IBAction) startListening: (id) sender; +- (IBAction) stopListening: (id) sender; + +// is this remote control sending the given event? +- (BOOL) sendsEventForButtonIdentifier: (RemoteControlEventIdentifier) identifier; + +// sending of notifications between applications ++ (void) sendFinishedNotifcationForAppIdentifier: (NSString*) identifier; ++ (void) sendRequestForRemoteControlNotification; + +// name of the device ++ (const char*) remoteControlDeviceName; + +@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControl.m b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControl.m new file mode 100644 index 0000000000..bf224dd1d8 --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControl.m @@ -0,0 +1,102 @@ +/***************************************************************************** + * RemoteControl.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import "RemoteControl.h" + +// notifaction names that are being used to signal that an application wants to +// have access to the remote control device or if the application has finished +// using the remote control device +NSString* REQUEST_FOR_REMOTE_CONTROL_NOTIFCATION = @"mac.remotecontrols.RequestForRemoteControl"; +NSString* FINISHED_USING_REMOTE_CONTROL_NOTIFICATION = @"mac.remotecontrols.FinishedUsingRemoteControl"; + +// keys used in user objects for distributed notifications +NSString* kRemoteControlDeviceName = @"RemoteControlDeviceName"; +NSString* kApplicationIdentifier = @"CFBundleIdentifier"; +// bundle identifier of the application that should get access to the remote control +// this key is being used in the FINISHED notification only +NSString* kTargetApplicationIdentifier = @"TargetBundleIdentifier"; + + +@implementation RemoteControl + +// returns nil if the remote control device is not available +- (id) initWithDelegate: (id) _remoteControlDelegate { + if (self = [super init]) { + delegate = _remoteControlDelegate; + } + return self; +} + +- (void) dealloc { + [super dealloc]; +} + +- (void) setListeningToRemote: (BOOL) value { +} +- (BOOL) isListeningToRemote { + return NO; +} + +- (IBAction) startListening: (id) sender { +} +- (IBAction) stopListening: (id) sender { + +} + +- (BOOL) isOpenInExclusiveMode { + return YES; +} +- (void) setOpenInExclusiveMode: (BOOL) value { +} + +- (BOOL) sendsEventForButtonIdentifier: (RemoteControlEventIdentifier) identifier { + return YES; +} + ++ (void) sendDistributedNotification: (NSString*) notificationName targetBundleIdentifier: (NSString*) targetIdentifier { + NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys: [NSString stringWithCString:[self remoteControlDeviceName] encoding:NSASCIIStringEncoding], + kRemoteControlDeviceName, [[NSBundle mainBundle] bundleIdentifier], kApplicationIdentifier, + targetIdentifier, kTargetApplicationIdentifier, nil]; + + [[NSDistributedNotificationCenter defaultCenter] postNotificationName:notificationName + object:nil + userInfo:userInfo + deliverImmediately:YES]; +} + ++ (void) sendFinishedNotifcationForAppIdentifier: (NSString*) identifier { + [self sendDistributedNotification:FINISHED_USING_REMOTE_CONTROL_NOTIFICATION targetBundleIdentifier:identifier]; +} ++ (void) sendRequestForRemoteControlNotification { + [self sendDistributedNotification:REQUEST_FOR_REMOTE_CONTROL_NOTIFCATION targetBundleIdentifier:nil]; +} + ++ (const char*) remoteControlDeviceName { + return NULL; +} + +@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControlContainer.h b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControlContainer.h new file mode 100644 index 0000000000..e665ae1eab --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControlContainer.h @@ -0,0 +1,38 @@ +/***************************************************************************** + * RemoteControlContainer.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import <Cocoa/Cocoa.h> +#import "RemoteControl.h" + +@interface RemoteControlContainer : RemoteControl { + NSMutableArray* remoteControls; +} + +- (BOOL) instantiateAndAddRemoteControlDeviceWithClass: (Class) clazz; +- (unsigned int) count; + +@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControlContainer.m b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControlContainer.m new file mode 100644 index 0000000000..8517ea733e --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControlContainer.m @@ -0,0 +1,114 @@ +/***************************************************************************** + * RemoteControlContainer.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import "RemoteControlContainer.h" + + +@implementation RemoteControlContainer + +- (id) initWithDelegate: (id) _remoteControlDelegate { + if (self = [super initWithDelegate:_remoteControlDelegate]) { + remoteControls = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void) dealloc { + [self stopListening: self]; + [remoteControls release]; + [super dealloc]; +} + +- (BOOL) instantiateAndAddRemoteControlDeviceWithClass: (Class) clazz { + RemoteControl* remoteControl = [[clazz alloc] initWithDelegate: delegate]; + if (remoteControl) { + [remoteControls addObject: remoteControl]; + [remoteControl addObserver: self forKeyPath:@"listeningToRemote" options:NSKeyValueObservingOptionNew context:nil]; + return YES; + } + return NO; +} + +- (unsigned int) count { + return [remoteControls count]; +} + +- (void) reset { + [self willChangeValueForKey:@"listeningToRemote"]; + [self didChangeValueForKey:@"listeningToRemote"]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + [self reset]; +} + +- (void) setListeningToRemote: (BOOL) value { + int i; + for(i=0; i < [remoteControls count]; i++) { + [[remoteControls objectAtIndex: i] setListeningToRemote: value]; + } + if (value && value != [self isListeningToRemote]) [self performSelector:@selector(reset) withObject:nil afterDelay:0.01]; +} +- (BOOL) isListeningToRemote { + int i; + for(i=0; i < [remoteControls count]; i++) { + if ([[remoteControls objectAtIndex: i] isListeningToRemote]) { + return YES; + } + } + return NO; +} + +- (IBAction) startListening: (id) sender { + int i; + for(i=0; i < [remoteControls count]; i++) { + [[remoteControls objectAtIndex: i] startListening: sender]; + } +} +- (IBAction) stopListening: (id) sender { + int i; + for(i=0; i < [remoteControls count]; i++) { + [[remoteControls objectAtIndex: i] stopListening: sender]; + } +} + +- (BOOL) isOpenInExclusiveMode { + BOOL mode = YES; + int i; + for(i=0; i < [remoteControls count]; i++) { + mode = mode && ([[remoteControls objectAtIndex: i] isOpenInExclusiveMode]); + } + return mode; +} +- (void) setOpenInExclusiveMode: (BOOL) value { + int i; + for(i=0; i < [remoteControls count]; i++) { + [[remoteControls objectAtIndex: i] setOpenInExclusiveMode:value]; + } +} + +@end diff --git a/tools/EventClients/Clients/OSXRemote/XBMCDebugHelpers.h b/tools/EventClients/Clients/OSXRemote/XBMCDebugHelpers.h new file mode 100644 index 0000000000..8650f87412 --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/XBMCDebugHelpers.h @@ -0,0 +1,45 @@ +// +// XBMCDebugHelpers.h +// xbmclauncher +// +// Created by Stephan Diederich on 21.09.08. +// Copyright 2008 University Heidelberg. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#import <Cocoa/Cocoa.h> + +/* + * Stuff below was taken from LoggingUtils.h of ATVFiles + * + * Just some utility macros for logging... + * + * Created by Eric Steil III on 4/1/07. + // Copyright (C) 2007-2008 Eric Steil III + * + */ + +#ifdef DEBUG +#define LOG(s, ...) NSLog(@"[DEBUG] " s, ##__VA_ARGS__) +#define ILOG(s, ...) NSLog(@"[INFO] " s, ##__VA_ARGS__) +#define ELOG(s, ...) NSLog(@"[ERROR] " s, ##__VA_ARGS__) +#define DLOG(s, ...) LOG(s, ##__VA_ARGS__) +#else +#define LOG(s, ...) +#define ILOG(s, ...) NSLog(@"[INFO] " s, ##__VA_ARGS__) +#define ELOG(s, ...) NSLog(@"[ERROR] " s, ##__VA_ARGS__) +#define DLOG(s, ...) LOG(s, ##__VA_ARGS__) +#endif + +#define PRINT_SIGNATURE() LOG(@"%s", __PRETTY_FUNCTION__) diff --git a/tools/EventClients/Clients/OSXRemote/XBMCHelper.h b/tools/EventClients/Clients/OSXRemote/XBMCHelper.h new file mode 100644 index 0000000000..9b194a9fd7 --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/XBMCHelper.h @@ -0,0 +1,29 @@ +// +// XBMCHelper.h +// xbmchelper +// +// Created by Stephan Diederich on 11/12/08. +// Copyright 2008 University Heidelberg. All rights reserved. +// + +#import <Cocoa/Cocoa.h> +#import "xbmcclientwrapper.h" + +@class AppleRemote, MultiClickRemoteBehavior; + +@interface XBMCHelper : NSObject { + AppleRemote* mp_remote_control; + XBMCClientWrapper* mp_wrapper; + NSString* mp_app_path; + NSString* mp_home_path; + bool m_verbose; +} + +- (void) enableVerboseMode:(bool) f_really; + +- (void) setApplicationPath:(NSString*) fp_app_path; +- (void) setApplicationHome:(NSString*) fp_home_path; + +- (void) connectToServer:(NSString*) fp_server withMode:(eRemoteMode) f_mode withTimeout:(double) f_timeout; +- (void) disconnect; +@end diff --git a/tools/EventClients/Clients/OSXRemote/XBMCHelper.m b/tools/EventClients/Clients/OSXRemote/XBMCHelper.m new file mode 100644 index 0000000000..5e0223e11a --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/XBMCHelper.m @@ -0,0 +1,222 @@ +// +// XBMCHelper.m +// xbmchelper +// +// Created by Stephan Diederich on 11/12/08. +// Copyright 2008 University Heidelberg. All rights reserved. +// + +#import "XBMCHelper.h" +#import "remotecontrolwrapper/AppleRemote.h" +#import "XBMCDebugHelpers.h" +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- +@implementation XBMCHelper +- (id) init{ + PRINT_SIGNATURE(); + if( ![super init] ){ + return nil; + } + mp_wrapper = nil; + mp_remote_control = [[[AppleRemote alloc] initWithDelegate: self] retain]; + [mp_remote_control setProcessesBacklog:true]; + [mp_remote_control setOpenInExclusiveMode:true]; + if( ! mp_remote_control ){ + NSException* myException = [NSException + exceptionWithName:@"AppleRemoteInitExecption" + reason:@"AppleRemote could not be initialized" + userInfo:nil]; + @throw myException; + } + [mp_remote_control startListening: self]; + if(![mp_remote_control isListeningToRemote]){ + ELOG(@"Warning: XBMCHelper could not open the IR-Device in exclusive mode. Other remote control apps running?"); + } + return self; +} + +- (void) dealloc{ + PRINT_SIGNATURE(); + [mp_remote_control stopListening: self]; + [mp_remote_control release]; + [mp_wrapper release]; + [mp_app_path release]; + [mp_home_path release]; + [super dealloc]; +} + +//---------------------------------------------------------------------------- +- (void) checkAndLaunchApp +{ + if(!mp_app_path || ![mp_app_path length]){ + ELOG(@"No executable set. Nothing to launch"); + return; + } + + NSFileManager *fileManager = [NSFileManager defaultManager]; + if(![fileManager fileExistsAtPath:mp_app_path]){ + ELOG(@"Path does not exist: %@. Cannot launch executable", mp_app_path); + return; + } + if(mp_home_path && [mp_home_path length]) + setenv("XBMC_HOME", [mp_home_path cString], 1); + //launch or activate xbmc + if(![[NSWorkspace sharedWorkspace] launchApplication:mp_app_path]){ + ELOG(@"Error launching %@", mp_app_path); + } +} + +//---------------------------------------------------------------------------- +- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown remoteControl: (RemoteControl*) remoteControl; +{ + if(m_verbose) { + //do some logging here + //[self logButton: event press; + } + + switch(event){ + case kRemoteButtonPlay: + if(pressedDown) [mp_wrapper handleEvent:ATV_BUTTON_PLAY]; + break; + case kRemoteButtonPlay_Hold: + if(pressedDown) [mp_wrapper handleEvent:ATV_BUTTON_PLAY_H]; + break; + case kRemoteButtonRight: + if(pressedDown) + [mp_wrapper handleEvent:ATV_BUTTON_RIGHT]; + else + [mp_wrapper handleEvent:ATV_BUTTON_RIGHT_RELEASE]; + break; + case kRemoteButtonRight_Hold: + if(pressedDown) + [mp_wrapper handleEvent:ATV_BUTTON_RIGHT_H]; + else + [mp_wrapper handleEvent:ATV_BUTTON_RIGHT_H_RELEASE]; + break; + case kRemoteButtonLeft: + if(pressedDown) + [mp_wrapper handleEvent:ATV_BUTTON_LEFT]; + else + [mp_wrapper handleEvent:ATV_BUTTON_LEFT_RELEASE]; + break; + case kRemoteButtonLeft_Hold: + if(pressedDown) + [mp_wrapper handleEvent:ATV_BUTTON_LEFT_H]; + else + [mp_wrapper handleEvent:ATV_BUTTON_LEFT_H_RELEASE]; + break; + case kRemoteButtonPlus: + if(pressedDown) + [mp_wrapper handleEvent:ATV_BUTTON_UP]; + else + [mp_wrapper handleEvent:ATV_BUTTON_UP_RELEASE]; + break; + case kRemoteButtonMinus: + if(pressedDown) + [mp_wrapper handleEvent:ATV_BUTTON_DOWN]; + else + [mp_wrapper handleEvent:ATV_BUTTON_DOWN_RELEASE]; + break; + case kRemoteButtonMenu: + if(pressedDown){ + [self checkAndLaunchApp]; //launch mp_app_path if it's not running + [mp_wrapper handleEvent:ATV_BUTTON_MENU]; + } + break; + case kRemoteButtonMenu_Hold: + if(pressedDown) [mp_wrapper handleEvent:ATV_BUTTON_MENU_H]; + break; + case kRemoteControl_Switched: + if(pressedDown) [mp_wrapper switchRemote: [mp_remote_control deviceID]]; + break; + default: + NSLog(@"Oha, remote button not recognized %i pressed/released %i", event, pressedDown); + } +} + +//---------------------------------------------------------------------------- +- (void) connectToServer:(NSString*) fp_server withMode:(eRemoteMode) f_mode withTimeout:(double) f_timeout{ + if(mp_wrapper) + [self disconnect]; + mp_wrapper = [[XBMCClientWrapper alloc] initWithMode:f_mode serverAddress:fp_server verbose:m_verbose]; + [mp_wrapper setUniversalModeTimeout:f_timeout]; +} + +//---------------------------------------------------------------------------- +- (void) disconnect{ + [mp_wrapper release]; + mp_wrapper = nil; +} + +//---------------------------------------------------------------------------- +- (void) enableVerboseMode:(bool) f_really{ + m_verbose = f_really; + [mp_wrapper enableVerboseMode:f_really]; +} + +//---------------------------------------------------------------------------- +- (void) setApplicationPath:(NSString*) fp_app_path{ + if (mp_app_path != fp_app_path) { + [mp_app_path release]; + mp_app_path = [[fp_app_path stringByStandardizingPath] retain]; + } +} + +//---------------------------------------------------------------------------- +- (void) setApplicationHome:(NSString*) fp_home_path{ + if (mp_home_path != fp_home_path) { + [mp_home_path release]; + mp_home_path = [[fp_home_path stringByStandardizingPath] retain]; + } +} +// NSString* pressed; +// NSString* buttonName; +// if (pressedDown) pressed = @"(pressed)"; else pressed = @"(released)"; +// +// switch(event) { +// case kRemoteButtonPlus: +// buttonName = @"Volume up"; +// break; +// case kRemoteButtonMinus: +// buttonName = @"Volume down"; +// break; +// case kRemoteButtonMenu: +// buttonName = @"Menu"; +// break; +// case kRemoteButtonPlay: +// buttonName = @"Play"; +// break; +// case kRemoteButtonRight: +// buttonName = @"Right"; +// break; +// case kRemoteButtonLeft: +// buttonName = @"Left"; +// break; +// case kRemoteButtonRight_Hold: +// buttonName = @"Right holding"; +// break; +// case kRemoteButtonLeft_Hold: +// buttonName = @"Left holding"; +// break; +// case kRemoteButtonPlus_Hold: +// buttonName = @"Volume up holding"; +// break; +// case kRemoteButtonMinus_Hold: +// buttonName = @"Volume down holding"; +// break; +// case kRemoteButtonPlay_Hold: +// buttonName = @"Play (sleep mode)"; +// break; +// case kRemoteButtonMenu_Hold: +// buttonName = @"Menu holding"; +// break; +// case kRemoteControl_Switched: +// buttonName = @"Remote Control Switched"; +// break; +// default: +// break; +// } +// NSLog(@"%@ %@", pressed, buttonName); +// } + +@end diff --git a/tools/EventClients/Clients/OSXRemote/XBMCHelper.xcodeproj/project.pbxproj b/tools/EventClients/Clients/OSXRemote/XBMCHelper.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..0c6c5ef7c9 --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/XBMCHelper.xcodeproj/project.pbxproj @@ -0,0 +1,305 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + E45F2B1C0F95191E007BCC0B /* IRKeyboardEmu.m in Sources */ = {isa = PBXBuildFile; fileRef = E45F2B1B0F95191E007BCC0B /* IRKeyboardEmu.m */; }; + E4E62F370F83DB760066AF9D /* xbmchelper_main.mm in Sources */ = {isa = PBXBuildFile; fileRef = E4E62F340F83DB760066AF9D /* xbmchelper_main.mm */; }; + E4E62F380F83DB760066AF9D /* XBMCHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = E4E62F360F83DB760066AF9D /* XBMCHelper.m */; }; + E4E62F4A0F83DB830066AF9D /* AppleRemote.m in Sources */ = {isa = PBXBuildFile; fileRef = E4E62F3D0F83DB830066AF9D /* AppleRemote.m */; }; + E4E62F4B0F83DB830066AF9D /* GlobalKeyboardDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = E4E62F3F0F83DB830066AF9D /* GlobalKeyboardDevice.m */; }; + E4E62F4C0F83DB830066AF9D /* HIDRemoteControlDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = E4E62F410F83DB830066AF9D /* HIDRemoteControlDevice.m */; }; + E4E62F4D0F83DB830066AF9D /* KeyspanFrontRowControl.m in Sources */ = {isa = PBXBuildFile; fileRef = E4E62F430F83DB830066AF9D /* KeyspanFrontRowControl.m */; }; + E4E62F4E0F83DB830066AF9D /* MultiClickRemoteBehavior.m in Sources */ = {isa = PBXBuildFile; fileRef = E4E62F450F83DB830066AF9D /* MultiClickRemoteBehavior.m */; }; + E4E62F4F0F83DB830066AF9D /* RemoteControl.m in Sources */ = {isa = PBXBuildFile; fileRef = E4E62F470F83DB830066AF9D /* RemoteControl.m */; }; + E4E62F500F83DB830066AF9D /* RemoteControlContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = E4E62F490F83DB830066AF9D /* RemoteControlContainer.m */; }; + E4E62F600F83FB8C0066AF9D /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4E62F5F0F83FB8C0066AF9D /* IOKit.framework */; }; + E4E62F690F83FBB40066AF9D /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4E62F680F83FBB40066AF9D /* Carbon.framework */; }; + E4E62FD40F83FD7C0066AF9D /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4E62FD30F83FD7C0066AF9D /* Cocoa.framework */; }; + E4E62FE80F83FDD90066AF9D /* xbmcclientwrapper.mm in Sources */ = {isa = PBXBuildFile; fileRef = E4E62FE60F83FDD90066AF9D /* xbmcclientwrapper.mm */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 8DD76F7B0486A8DE00D96B5E /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 8; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 09AB6884FE841BABC02AAC07 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = /System/Library/Frameworks/CoreFoundation.framework; sourceTree = "<absolute>"; }; + 8DD76F7E0486A8DE00D96B5E /* XBMCHelper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = XBMCHelper; sourceTree = BUILT_PRODUCTS_DIR; }; + E45F2B1A0F95191E007BCC0B /* IRKeyboardEmu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IRKeyboardEmu.h; sourceTree = "<group>"; }; + E45F2B1B0F95191E007BCC0B /* IRKeyboardEmu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IRKeyboardEmu.m; sourceTree = "<group>"; }; + E4E62F340F83DB760066AF9D /* xbmchelper_main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = xbmchelper_main.mm; sourceTree = "<group>"; }; + E4E62F350F83DB760066AF9D /* XBMCHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XBMCHelper.h; sourceTree = "<group>"; }; + E4E62F360F83DB760066AF9D /* XBMCHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XBMCHelper.m; sourceTree = "<group>"; }; + E4E62F3C0F83DB830066AF9D /* AppleRemote.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppleRemote.h; sourceTree = "<group>"; }; + E4E62F3D0F83DB830066AF9D /* AppleRemote.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppleRemote.m; sourceTree = "<group>"; }; + E4E62F3E0F83DB830066AF9D /* GlobalKeyboardDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GlobalKeyboardDevice.h; sourceTree = "<group>"; }; + E4E62F3F0F83DB830066AF9D /* GlobalKeyboardDevice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GlobalKeyboardDevice.m; sourceTree = "<group>"; }; + E4E62F400F83DB830066AF9D /* HIDRemoteControlDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HIDRemoteControlDevice.h; sourceTree = "<group>"; }; + E4E62F410F83DB830066AF9D /* HIDRemoteControlDevice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HIDRemoteControlDevice.m; sourceTree = "<group>"; }; + E4E62F420F83DB830066AF9D /* KeyspanFrontRowControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyspanFrontRowControl.h; sourceTree = "<group>"; }; + E4E62F430F83DB830066AF9D /* KeyspanFrontRowControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KeyspanFrontRowControl.m; sourceTree = "<group>"; }; + E4E62F440F83DB830066AF9D /* MultiClickRemoteBehavior.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultiClickRemoteBehavior.h; sourceTree = "<group>"; }; + E4E62F450F83DB830066AF9D /* MultiClickRemoteBehavior.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultiClickRemoteBehavior.m; sourceTree = "<group>"; }; + E4E62F460F83DB830066AF9D /* RemoteControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RemoteControl.h; sourceTree = "<group>"; }; + E4E62F470F83DB830066AF9D /* RemoteControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RemoteControl.m; sourceTree = "<group>"; }; + E4E62F480F83DB830066AF9D /* RemoteControlContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RemoteControlContainer.h; sourceTree = "<group>"; }; + E4E62F490F83DB830066AF9D /* RemoteControlContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RemoteControlContainer.m; sourceTree = "<group>"; }; + E4E62F5F0F83FB8C0066AF9D /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; + E4E62F680F83FBB40066AF9D /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; + E4E62FD30F83FD7C0066AF9D /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + E4E62FE50F83FDD90066AF9D /* xbmcclientwrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xbmcclientwrapper.h; sourceTree = "<group>"; }; + E4E62FE60F83FDD90066AF9D /* xbmcclientwrapper.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = xbmcclientwrapper.mm; sourceTree = "<group>"; }; + E4E62FE70F83FDD90066AF9D /* XBMCDebugHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XBMCDebugHelpers.h; sourceTree = "<group>"; }; + E4E630030F8406900066AF9D /* xbmcclient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = xbmcclient.h; path = "../../lib/c++/xbmcclient.h"; sourceTree = SOURCE_ROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8DD76F780486A8DE00D96B5E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E4E62F600F83FB8C0066AF9D /* IOKit.framework in Frameworks */, + E4E62F690F83FBB40066AF9D /* Carbon.framework in Frameworks */, + E4E62FD40F83FD7C0066AF9D /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* XBMCHelper */ = { + isa = PBXGroup; + children = ( + 08FB7795FE84155DC02AAC07 /* Source */, + C6859E96029091FE04C91782 /* Documentation */, + 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */, + 19C28FBDFE9D53C911CA2CBB /* Products */, + E4E62F5F0F83FB8C0066AF9D /* IOKit.framework */, + E4E62F680F83FBB40066AF9D /* Carbon.framework */, + E4E62FD30F83FD7C0066AF9D /* Cocoa.framework */, + ); + name = XBMCHelper; + sourceTree = "<group>"; + }; + 08FB7795FE84155DC02AAC07 /* Source */ = { + isa = PBXGroup; + children = ( + E4E62F3B0F83DB830066AF9D /* RemoteControlWrapper */, + E4E630030F8406900066AF9D /* xbmcclient.h */, + E4E62F340F83DB760066AF9D /* xbmchelper_main.mm */, + E4E62F350F83DB760066AF9D /* XBMCHelper.h */, + E4E62F360F83DB760066AF9D /* XBMCHelper.m */, + E4E62FE70F83FDD90066AF9D /* XBMCDebugHelpers.h */, + E4E62FE50F83FDD90066AF9D /* xbmcclientwrapper.h */, + E4E62FE60F83FDD90066AF9D /* xbmcclientwrapper.mm */, + ); + name = Source; + sourceTree = "<group>"; + }; + 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */ = { + isa = PBXGroup; + children = ( + 09AB6884FE841BABC02AAC07 /* CoreFoundation.framework */, + ); + name = "External Frameworks and Libraries"; + sourceTree = "<group>"; + }; + 19C28FBDFE9D53C911CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 8DD76F7E0486A8DE00D96B5E /* XBMCHelper */, + ); + name = Products; + sourceTree = "<group>"; + }; + C6859E96029091FE04C91782 /* Documentation */ = { + isa = PBXGroup; + children = ( + ); + name = Documentation; + sourceTree = "<group>"; + }; + E4E62F3B0F83DB830066AF9D /* RemoteControlWrapper */ = { + isa = PBXGroup; + children = ( + E4E62F3C0F83DB830066AF9D /* AppleRemote.h */, + E4E62F3D0F83DB830066AF9D /* AppleRemote.m */, + E4E62F3E0F83DB830066AF9D /* GlobalKeyboardDevice.h */, + E4E62F3F0F83DB830066AF9D /* GlobalKeyboardDevice.m */, + E4E62F400F83DB830066AF9D /* HIDRemoteControlDevice.h */, + E4E62F410F83DB830066AF9D /* HIDRemoteControlDevice.m */, + E4E62F420F83DB830066AF9D /* KeyspanFrontRowControl.h */, + E4E62F430F83DB830066AF9D /* KeyspanFrontRowControl.m */, + E4E62F440F83DB830066AF9D /* MultiClickRemoteBehavior.h */, + E4E62F450F83DB830066AF9D /* MultiClickRemoteBehavior.m */, + E4E62F460F83DB830066AF9D /* RemoteControl.h */, + E4E62F470F83DB830066AF9D /* RemoteControl.m */, + E4E62F480F83DB830066AF9D /* RemoteControlContainer.h */, + E4E62F490F83DB830066AF9D /* RemoteControlContainer.m */, + E45F2B1A0F95191E007BCC0B /* IRKeyboardEmu.h */, + E45F2B1B0F95191E007BCC0B /* IRKeyboardEmu.m */, + ); + path = RemoteControlWrapper; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8DD76F740486A8DE00D96B5E /* XBMCHelper */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB924708733DCA0010E9CD /* Build configuration list for PBXNativeTarget "XBMCHelper" */; + buildPhases = ( + 8DD76F760486A8DE00D96B5E /* Sources */, + 8DD76F780486A8DE00D96B5E /* Frameworks */, + 8DD76F7B0486A8DE00D96B5E /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = XBMCHelper; + productInstallPath = "$(HOME)/bin"; + productName = XBMCHelper; + productReference = 8DD76F7E0486A8DE00D96B5E /* XBMCHelper */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 1DEB924B08733DCA0010E9CD /* Build configuration list for PBXProject "XBMCHelper" */; + compatibilityVersion = "Xcode 3.1"; + hasScannedForEncodings = 1; + mainGroup = 08FB7794FE84155DC02AAC07 /* XBMCHelper */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8DD76F740486A8DE00D96B5E /* XBMCHelper */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 8DD76F760486A8DE00D96B5E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E4E62F370F83DB760066AF9D /* xbmchelper_main.mm in Sources */, + E4E62F380F83DB760066AF9D /* XBMCHelper.m in Sources */, + E4E62F4A0F83DB830066AF9D /* AppleRemote.m in Sources */, + E4E62F4B0F83DB830066AF9D /* GlobalKeyboardDevice.m in Sources */, + E4E62F4C0F83DB830066AF9D /* HIDRemoteControlDevice.m in Sources */, + E4E62F4D0F83DB830066AF9D /* KeyspanFrontRowControl.m in Sources */, + E4E62F4E0F83DB830066AF9D /* MultiClickRemoteBehavior.m in Sources */, + E4E62F4F0F83DB830066AF9D /* RemoteControl.m in Sources */, + E4E62F500F83DB830066AF9D /* RemoteControlContainer.m in Sources */, + E4E62FE80F83FDD90066AF9D /* xbmcclientwrapper.mm in Sources */, + E45F2B1C0F95191E007BCC0B /* IRKeyboardEmu.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1DEB924808733DCA0010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CONFIGURATION_BUILD_DIR = ../../../osx; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = DEBUG; + INSTALL_PATH = /usr/local/bin; + ONLY_ACTIVE_ARCH = NO; + PRODUCT_NAME = XBMCHelper; + SDKROOT = macosx10.4; + STRIP_STYLE = debugging; + }; + name = Debug; + }; + 1DEB924908733DCA0010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CONFIGURATION_BUILD_DIR = ../../../osx; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_MODEL_TUNING = G5; + GCC_VERSION = 4.0; + INSTALL_PATH = /usr/local/bin; + ONLY_ACTIVE_ARCH = NO; + PRODUCT_NAME = XBMCHelper; + SDKROOT = macosx10.4; + STRIP_STYLE = debugging; + }; + name = Release; + }; + 1DEB924C08733DCA0010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_VERSION = 4.0; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + ONLY_ACTIVE_ARCH = YES; + PREBINDING = NO; + SDKROOT = macosx10.4; + }; + name = Debug; + }; + 1DEB924D08733DCA0010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PREBINDING = NO; + SDKROOT = macosx10.4; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB924708733DCA0010E9CD /* Build configuration list for PBXNativeTarget "XBMCHelper" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB924808733DCA0010E9CD /* Debug */, + 1DEB924908733DCA0010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB924B08733DCA0010E9CD /* Build configuration list for PBXProject "XBMCHelper" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB924C08733DCA0010E9CD /* Debug */, + 1DEB924D08733DCA0010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +} diff --git a/tools/EventClients/Clients/OSXRemote/xbmcclientwrapper.h b/tools/EventClients/Clients/OSXRemote/xbmcclientwrapper.h new file mode 100644 index 0000000000..8664b2d985 --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/xbmcclientwrapper.h @@ -0,0 +1,73 @@ +/* + * xbmcclient.cpp + * xbmclauncher + * + * Created by Stephan Diederich on 17.09.08. + * Copyright 2008 Stephan Diederich. All rights reserved. + * + */ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +#import <Cocoa/Cocoa.h> + +typedef enum{ + ATV_BUTTON_DONT_USE_THIS = 0, //don't use zero, as those enums get converted to strings later + ATV_BUTTON_PLAY=1, + ATV_BUTTON_PLAY_H, //present on ATV>=2.2 + ATV_BUTTON_RIGHT, + ATV_BUTTON_RIGHT_RELEASE, + ATV_BUTTON_RIGHT_H, //present on ATV<=2.1 and OSX v? + ATV_BUTTON_RIGHT_H_RELEASE, + ATV_BUTTON_LEFT, + ATV_BUTTON_LEFT_RELEASE, + ATV_BUTTON_LEFT_H, //present on ATV<=2.1 and OSX v? + ATV_BUTTON_LEFT_H_RELEASE, + ATV_BUTTON_UP, + ATV_BUTTON_UP_RELEASE, + ATV_BUTTON_DOWN, + ATV_BUTTON_DOWN_RELEASE, + ATV_BUTTON_MENU, + ATV_BUTTON_MENU_H, + ATV_LEARNED_PLAY, + ATV_LEARNED_PAUSE, + ATV_LEARNED_STOP, + ATV_LEARNED_PREVIOUS, + ATV_LEARNED_NEXT, + ATV_LEARNED_REWIND, //>=ATV 2.3 + ATV_LEARNED_REWIND_RELEASE, //>=ATV 2.3 + ATV_LEARNED_FORWARD, //>=ATV 2.3 + ATV_LEARNED_FORWARD_RELEASE, //>=ATV 2.3 + ATV_LEARNED_RETURN, + ATV_LEARNED_ENTER, + ATV_INVALID_BUTTON +} eATVClientEvent; + + +typedef enum { + DEFAULT_MODE, + UNIVERSAL_MODE, + MULTIREMOTE_MODE +} eRemoteMode; + + +@interface XBMCClientWrapper : NSObject{ + struct XBMCClientWrapperImpl* mp_impl; +} +- (id) initWithMode:(eRemoteMode) f_mode serverAddress:(NSString*) fp_server verbose:(bool) f_verbose; +- (void) setUniversalModeTimeout:(double) f_timeout; + +-(void) handleEvent:(eATVClientEvent) f_event; +-(void) switchRemote:(int) f_device_id; + +- (void) enableVerboseMode:(bool) f_really; +@end
\ No newline at end of file diff --git a/tools/EventClients/Clients/OSXRemote/xbmcclientwrapper.mm b/tools/EventClients/Clients/OSXRemote/xbmcclientwrapper.mm new file mode 100644 index 0000000000..ab4d0de655 --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/xbmcclientwrapper.mm @@ -0,0 +1,430 @@ +/* + * xbmcclient.cpp + * xbmclauncher + * + * Created by Stephan Diederich on 17.09.08. + * Copyright 2008 University Heidelberg. All rights reserved. + * + */ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "xbmcclientwrapper.h" +#include "../../lib/c++/xbmcclient.h" +#include "XBMCDebugHelpers.h" +#include <vector> +#include <map> +#include <string> +#include <sstream> + +//helper class for easy EventSequence handling +class XBMCClientEventSequence{ +public: + XBMCClientEventSequence(){} + + //implicit conversion + XBMCClientEventSequence(eATVClientEvent f_event){ + m_stream.push_back(f_event); + } + + std::string str() const{ + std::stringstream ss; + for(std::vector<eATVClientEvent>::const_iterator it = m_stream.begin(); + it != m_stream.end(); + ++it){ + ss << *it; + } + return ss.str(); + } + void clear(){ + m_stream.clear(); + } + + // + // operators + // + friend XBMCClientEventSequence operator+ (XBMCClientEventSequence f_seq, eATVClientEvent f_event){ + f_seq.m_stream.push_back(f_event); + return f_seq; + } + XBMCClientEventSequence& operator << (eATVClientEvent f_event){ + m_stream.push_back(f_event); + return *this; + } + friend bool operator <(XBMCClientEventSequence const& fcr_lhs,XBMCClientEventSequence const& fcr_rhs){ + return fcr_lhs.m_stream < fcr_rhs.m_stream; + } + friend bool operator ==(XBMCClientEventSequence const& fcr_lhs,XBMCClientEventSequence const& fcr_rhs){ + return fcr_lhs.m_stream == fcr_rhs.m_stream; + } +private: + std::vector<eATVClientEvent> m_stream; +}; + + +//typedef is here, as is seems that I can't put it into iterface declaration +//CPacketBUTTON is a pointer, as I'm not sure how well it's copy constructor is implemented +typedef std::map<eATVClientEvent, CPacketBUTTON*> tEventMap; +typedef std::map<XBMCClientEventSequence, CPacketBUTTON*> tSequenceMap; +typedef std::map<std::pair<int, eATVClientEvent>, CPacketBUTTON*> tMultiRemoteMap; + +class XBMCClientWrapperImpl{ + tEventMap m_event_map; + tSequenceMap m_sequence_map; + tMultiRemoteMap m_multiremote_map; + eRemoteMode m_mode; + int m_socket; + std::string m_address; + XBMCClientEventSequence m_sequence; + CFRunLoopTimerRef m_timer; + double m_sequence_timeout; + int m_device_id; + bool m_verbose_mode; + void populateEventMap(); + void populateSequenceMap(); + void populateMultiRemoteModeMap(); + void sendButton(eATVClientEvent f_event); + void sendSequence(); + void restartTimer(); + void resetTimer(); + bool isStartToken(eATVClientEvent f_event); + static void timerCallBack (CFRunLoopTimerRef timer, void *info); +public: + XBMCClientWrapperImpl(eRemoteMode f_mode, const std::string& fcr_address = "localhost", bool f_verbose_mode=false); + ~XBMCClientWrapperImpl(); + void setUniversalModeTimeout(double f_timeout){ + m_sequence_timeout = f_timeout; + } + void switchRemote(int f_device_id){ + m_device_id = f_device_id; + } + void handleEvent(eATVClientEvent f_event); + void enableVerboseMode(bool f_value){ + m_verbose_mode = f_value; + } +}; + +void XBMCClientWrapperImpl::timerCallBack (CFRunLoopTimerRef timer, void *info) +{ + if (!info) + { + fprintf(stderr, "Error. invalid argument to timer callback\n"); + return; + } + + XBMCClientWrapperImpl *p_impl = (XBMCClientWrapperImpl *)info; + p_impl->sendSequence(); + p_impl->resetTimer(); +} + +void XBMCClientWrapperImpl::resetTimer(){ + if (m_timer) + { + CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), m_timer, kCFRunLoopCommonModes); + CFRunLoopTimerInvalidate(m_timer); + CFRelease(m_timer); + m_timer = NULL; + } +} + +void XBMCClientWrapperImpl::restartTimer(){ + if (m_timer) + resetTimer(); + + CFRunLoopTimerContext context = { 0, this, 0, 0, 0 }; + m_timer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + m_sequence_timeout, 0, 0, 0, timerCallBack, &context); + CFRunLoopAddTimer(CFRunLoopGetCurrent(), m_timer, kCFRunLoopCommonModes); +} + +XBMCClientWrapperImpl::XBMCClientWrapperImpl(eRemoteMode f_mode, const std::string& fcr_address, bool f_verbose_mode): +m_mode(f_mode), m_address(fcr_address), m_timer(0), m_sequence_timeout(0.5), m_device_id(150), m_verbose_mode(f_verbose_mode){ + PRINT_SIGNATURE(); + + if(m_mode == MULTIREMOTE_MODE){ + if(m_verbose_mode) + NSLog(@"XBMCClientWrapperImpl started in multiremote mode sending to address %s", fcr_address.c_str()); + populateMultiRemoteModeMap(); + } else { + if(m_mode == UNIVERSAL_MODE){ + if(m_verbose_mode) + NSLog(@"XBMCClientWrapperImpl started in universal mode sending to address %s", fcr_address.c_str()); + populateSequenceMap(); + } else if(m_verbose_mode) + NSLog(@"XBMCClientWrapperImpl started in normal mode sending to address %s", fcr_address.c_str()); + populateEventMap(); + } + + //open udp port etc + m_socket = socket(AF_INET, SOCK_DGRAM, 0); + if (m_socket < 0) + { + ELOG(@"Error opening UDP socket! error: ", errno); + //TODO What to do? + } +} + +namespace { + struct delete_second{ + template <class T> + void operator ()(T& fr_pair){ + delete fr_pair.second; + } + }; +} +XBMCClientWrapperImpl::~XBMCClientWrapperImpl(){ + PRINT_SIGNATURE(); + resetTimer(); + shutdown(m_socket, SHUT_RDWR); + std::for_each(m_event_map.begin(), m_event_map.end(), delete_second()); + std::for_each(m_sequence_map.begin(), m_sequence_map.end(), delete_second()); + std::for_each(m_multiremote_map.begin(), m_multiremote_map.end(), delete_second()); +} + +bool XBMCClientWrapperImpl::isStartToken(eATVClientEvent f_event){ + return f_event==ATV_BUTTON_MENU_H; +} + +void XBMCClientWrapperImpl::sendButton(eATVClientEvent f_event){ + CPacketBUTTON* lp_packet = 0; + if(m_mode == MULTIREMOTE_MODE){ + tMultiRemoteMap::iterator it = m_multiremote_map.find(std::make_pair(m_device_id, f_event)); + if(it == m_multiremote_map.end()){ + ELOG(@"XBMCClientWrapperImpl::sendButton: No mapping defined for remoteID: %i button %i", m_device_id, f_event); + return; + } + lp_packet = it->second; + } else { + tEventMap::iterator it = m_event_map.find(f_event); + if(it == m_event_map.end()){ + ELOG(@"XBMCClientWrapperImpl::sendButton: No mapping defined for button %i", f_event); + return; + } + lp_packet = it->second; + } + assert(lp_packet); + CAddress addr(m_address.c_str()); + if(m_verbose_mode) + NSLog(@"XBMCClientWrapperImpl::sendButton sending button %i down:%i up:%i", lp_packet->GetButtonCode(), lp_packet->GetFlags()&BTN_DOWN,lp_packet->GetFlags()&BTN_UP ); + lp_packet->Send(m_socket, addr); +} + +void XBMCClientWrapperImpl::sendSequence(){ + tSequenceMap::const_iterator it = m_sequence_map.find(m_sequence); + if(it != m_sequence_map.end()){ + CPacketBUTTON& packet = *(it->second); + CAddress addr(m_address.c_str()); + packet.Send(m_socket, addr); + if(m_verbose_mode) + NSLog(@"XBMCClientWrapperImpl::sendSequence sent sequence %i down:%i up:%i", packet.GetButtonCode(), packet.GetFlags()&BTN_DOWN,packet.GetFlags()&BTN_UP ); + } else { + ELOG(@"XBMCClientWrapperImpl::sendSequence: No mapping defined for sequence %s", m_sequence.str().c_str()); + } + m_sequence.clear(); +} + +void XBMCClientWrapperImpl::handleEvent(eATVClientEvent f_event){ + if(m_mode != UNIVERSAL_MODE){ + sendButton(f_event); + } else { + //in universal mode no keys are directly send. instead a key-sequence is assembled and a timer started + //when the timer expires, that key sequence is checked against predefined sequences and if it is a valid one, + //a button press is generated + if(m_sequence.str().empty()){ + if(isStartToken(f_event)){ + m_sequence << f_event; + DLOG(@"Starting sequence with token %s", m_sequence.str().c_str()); + restartTimer(); + } else { + sendButton(f_event); + } + } else { + //dont queue release-events but restart timer + if(f_event == ATV_BUTTON_LEFT_RELEASE || f_event == ATV_BUTTON_RIGHT_RELEASE || f_event == ATV_BUTTON_UP_RELEASE || f_event == ATV_BUTTON_DOWN_RELEASE) + DLOG(@"Discarded button up event for sequence"); + else{ + m_sequence << f_event; + DLOG(@"Extended sequence to %s", m_sequence.str().c_str()); + } + restartTimer(); + } + } +} + +void XBMCClientWrapperImpl::populateEventMap(){ + tEventMap& lr_map = m_event_map; + + lr_map.insert(std::make_pair(ATV_BUTTON_PLAY, new CPacketBUTTON(5, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_BUTTON_RIGHT, new CPacketBUTTON(4, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_BUTTON_RIGHT_RELEASE, new CPacketBUTTON(4, "JS0:AppleRemote", BTN_UP | BTN_NO_REPEAT | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_BUTTON_LEFT, new CPacketBUTTON(3, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT| BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_BUTTON_LEFT_RELEASE, new CPacketBUTTON(3, "JS0:AppleRemote", BTN_UP | BTN_NO_REPEAT | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_BUTTON_MENU, new CPacketBUTTON(6, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_BUTTON_MENU_H, new CPacketBUTTON(8, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_BUTTON_UP, new CPacketBUTTON(1, "JS0:AppleRemote", BTN_DOWN | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_BUTTON_UP_RELEASE, new CPacketBUTTON(1, "JS0:AppleRemote", BTN_UP | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_BUTTON_DOWN, new CPacketBUTTON(2, "JS0:AppleRemote", BTN_DOWN | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_BUTTON_DOWN_RELEASE, new CPacketBUTTON(2, "JS0:AppleRemote", BTN_UP | BTN_QUEUE))); + + // only present on ATV <= 2.1 <--- check that; OSX seems to have the release parts + lr_map.insert(std::make_pair(ATV_BUTTON_RIGHT_H, new CPacketBUTTON(10, "JS0:AppleRemote", BTN_DOWN | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_BUTTON_RIGHT_H_RELEASE, new CPacketBUTTON(10, "JS0:AppleRemote", BTN_UP | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_BUTTON_LEFT_H, new CPacketBUTTON(11, "JS0:AppleRemote", BTN_DOWN | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_BUTTON_LEFT_H_RELEASE, new CPacketBUTTON(11, "JS0:AppleRemote", BTN_UP | BTN_QUEUE))); + + // only present on atv >= 2.2 + lr_map.insert(std::make_pair(ATV_BUTTON_PLAY_H, new CPacketBUTTON(7, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + + //learned remote buttons (ATV >=2.3) + lr_map.insert(std::make_pair(ATV_LEARNED_PLAY, new CPacketBUTTON(70, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_LEARNED_PAUSE, new CPacketBUTTON(71, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_LEARNED_STOP, new CPacketBUTTON(72, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_LEARNED_PREVIOUS, new CPacketBUTTON(73, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_LEARNED_NEXT, new CPacketBUTTON(74, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_LEARNED_REWIND, new CPacketBUTTON(75, "JS0:AppleRemote", BTN_DOWN | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_LEARNED_REWIND_RELEASE, new CPacketBUTTON(75, "JS0:AppleRemote", BTN_UP | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_LEARNED_FORWARD, new CPacketBUTTON(76, "JS0:AppleRemote", BTN_DOWN | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_LEARNED_FORWARD_RELEASE, new CPacketBUTTON(76, "JS0:AppleRemote", BTN_UP | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_LEARNED_RETURN, new CPacketBUTTON(77, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + lr_map.insert(std::make_pair(ATV_LEARNED_ENTER, new CPacketBUTTON(78, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); +} + +void XBMCClientWrapperImpl::populateSequenceMap(){ + XBMCClientEventSequence sequence_prefix; + sequence_prefix << ATV_BUTTON_MENU_H; + m_sequence_map.insert(std::make_pair(sequence_prefix, new CPacketBUTTON(8, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_PLAY, new CPacketBUTTON(20, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_RIGHT, new CPacketBUTTON(21, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_LEFT, new CPacketBUTTON(22, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_UP, new CPacketBUTTON(23, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_DOWN, new CPacketBUTTON(24, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_MENU, new CPacketBUTTON(25, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + + sequence_prefix.clear(); + sequence_prefix << ATV_BUTTON_MENU_H << ATV_BUTTON_PLAY; + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_PLAY, new CPacketBUTTON(26, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_RIGHT, new CPacketBUTTON(27, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_LEFT, new CPacketBUTTON(28, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_UP, new CPacketBUTTON(29, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_DOWN, new CPacketBUTTON(30, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_MENU, new CPacketBUTTON(31, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + + sequence_prefix.clear(); + sequence_prefix << ATV_BUTTON_MENU_H << ATV_BUTTON_UP; + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_PLAY, new CPacketBUTTON(32, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_RIGHT, new CPacketBUTTON(33, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_LEFT, new CPacketBUTTON(34, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_UP, new CPacketBUTTON(35, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_DOWN, new CPacketBUTTON(36, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_MENU, new CPacketBUTTON(37, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + + sequence_prefix.clear(); + sequence_prefix << ATV_BUTTON_MENU_H << ATV_BUTTON_DOWN; + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_PLAY, new CPacketBUTTON(38, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_RIGHT, new CPacketBUTTON(39, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_LEFT, new CPacketBUTTON(40, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_UP, new CPacketBUTTON(41, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_DOWN, new CPacketBUTTON(42, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_MENU, new CPacketBUTTON(43, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + + sequence_prefix.clear(); + sequence_prefix << ATV_BUTTON_MENU_H << ATV_BUTTON_RIGHT; + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_PLAY, new CPacketBUTTON(44, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_RIGHT, new CPacketBUTTON(45, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_LEFT, new CPacketBUTTON(46, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_UP, new CPacketBUTTON(47, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_DOWN, new CPacketBUTTON(48, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_MENU, new CPacketBUTTON(49, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + + sequence_prefix.clear(); + sequence_prefix << ATV_BUTTON_MENU_H << ATV_BUTTON_LEFT; + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_PLAY, new CPacketBUTTON(50, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_RIGHT, new CPacketBUTTON(51, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_LEFT, new CPacketBUTTON(52, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_UP, new CPacketBUTTON(53, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_DOWN, new CPacketBUTTON(54, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_sequence_map.insert(std::make_pair( sequence_prefix + ATV_BUTTON_MENU, new CPacketBUTTON(55, "JS0:AppleRemote", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); +} + +void XBMCClientWrapperImpl::populateMultiRemoteModeMap(){ + //the example of harmony as a multi-remote uses a few key device-id's paired with the normal buttons + static int device_ids[] = {150, 151, 152, 153, 154, 155, 157, 158, 159, 160}; + int offset = 0; + for(int* device_id = device_ids; device_id != device_ids + sizeof(device_ids)/sizeof(*device_ids); ++device_id, offset += 10) + { + // keymaps for mult-apple-remote, including the device-key sent after remote-switch + // we just add them here with unique button numbers and do the real mapping in keymap.xml + // as an offset for the buttons we use the device + + // this loop should probably be replaced by a proper setting of the individual keys + // cons: currently only button-codes (aka ints) are sent + // way too lazy ;) + // pro: custom tweaks. e.g. button 1 on the harmony may be (153, ATV_BUTTON_LEFT) and this should not get a repeat + // maybe use the loop and tweak individual buttons later; plex maps here to strings, and later in keymap.xml to other strings, + // but this may need another kind of remote in XBMC source + m_multiremote_map.insert(std::make_pair(std::make_pair(*device_id,ATV_BUTTON_UP), new CPacketBUTTON(1 + offset, "JS0:Harmony", BTN_DOWN | BTN_QUEUE))); + m_multiremote_map.insert(std::make_pair(std::make_pair(*device_id,ATV_BUTTON_UP_RELEASE), new CPacketBUTTON(1 + offset, "JS0:Harmony", BTN_UP | BTN_QUEUE))); + m_multiremote_map.insert(std::make_pair(std::make_pair(*device_id,ATV_BUTTON_DOWN), new CPacketBUTTON(2 + offset, "JS0:Harmony", BTN_DOWN | BTN_QUEUE))); + m_multiremote_map.insert(std::make_pair(std::make_pair(*device_id,ATV_BUTTON_DOWN_RELEASE), new CPacketBUTTON(2 + offset, "JS0:Harmony", BTN_UP | BTN_QUEUE))); + + m_multiremote_map.insert(std::make_pair(std::make_pair(*device_id,ATV_BUTTON_LEFT), new CPacketBUTTON(3 + offset, "JS0:Harmony", BTN_DOWN | BTN_QUEUE))); + m_multiremote_map.insert(std::make_pair(std::make_pair(*device_id,ATV_BUTTON_LEFT_RELEASE), new CPacketBUTTON(3 + offset, "JS0:Harmony", BTN_UP | BTN_QUEUE))); + m_multiremote_map.insert(std::make_pair(std::make_pair(*device_id,ATV_BUTTON_RIGHT), new CPacketBUTTON(4 + offset, "JS0:Harmony", BTN_DOWN | BTN_QUEUE))); + m_multiremote_map.insert(std::make_pair(std::make_pair(*device_id,ATV_BUTTON_RIGHT_RELEASE), new CPacketBUTTON(4 + offset, "JS0:Harmony", BTN_UP | BTN_QUEUE))); + + m_multiremote_map.insert(std::make_pair(std::make_pair(*device_id,ATV_BUTTON_PLAY), new CPacketBUTTON(5 + offset, "JS0:Harmony", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_multiremote_map.insert(std::make_pair(std::make_pair(*device_id,ATV_BUTTON_MENU), new CPacketBUTTON(6 + offset, "JS0:Harmony", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_multiremote_map.insert(std::make_pair(std::make_pair(*device_id,ATV_BUTTON_PLAY_H), new CPacketBUTTON(7 + offset, "JS0:Harmony", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + m_multiremote_map.insert(std::make_pair(std::make_pair(*device_id,ATV_BUTTON_MENU_H), new CPacketBUTTON(8 + offset, "JS0:Harmony", BTN_DOWN | BTN_NO_REPEAT | BTN_QUEUE))); + + m_multiremote_map.insert(std::make_pair(std::make_pair(*device_id,ATV_BUTTON_RIGHT_H), new CPacketBUTTON(9 + offset, "JS0:Harmony", BTN_DOWN | BTN_QUEUE))); + m_multiremote_map.insert(std::make_pair(std::make_pair(*device_id,ATV_BUTTON_RIGHT_H_RELEASE),new CPacketBUTTON(9 + offset, "JS0:Harmony", BTN_UP | BTN_QUEUE))); + + m_multiremote_map.insert(std::make_pair(std::make_pair(*device_id,ATV_BUTTON_LEFT_H), new CPacketBUTTON(10 + offset, "JS0:Harmony", BTN_DOWN | BTN_QUEUE))); + m_multiremote_map.insert(std::make_pair(std::make_pair(*device_id,ATV_BUTTON_LEFT_H_RELEASE),new CPacketBUTTON(10 + offset, "JS0:Harmony", BTN_UP | BTN_QUEUE))); + } +} + +@implementation XBMCClientWrapper +- (id) init { + return [self initWithMode:DEFAULT_MODE serverAddress:@"localhost" verbose: false]; +} +- (id) initWithMode:(eRemoteMode) f_mode serverAddress:(NSString*) fp_server verbose:(bool) f_verbose{ + PRINT_SIGNATURE(); + if( ![super init] ) + return nil; + mp_impl = new XBMCClientWrapperImpl(f_mode, [fp_server UTF8String], f_verbose); + return self; +} + +- (void) setUniversalModeTimeout:(double) f_timeout{ + mp_impl->setUniversalModeTimeout(f_timeout); +} + +- (void)dealloc{ + PRINT_SIGNATURE(); + delete mp_impl; + [super dealloc]; +} + +-(void) handleEvent:(eATVClientEvent) f_event{ + mp_impl->handleEvent(f_event); +} + +-(void) switchRemote:(int) f_device_id{ + mp_impl->switchRemote(f_device_id); +} + +- (void) enableVerboseMode:(bool) f_really{ + mp_impl->enableVerboseMode(f_really); +} +@end diff --git a/tools/EventClients/Clients/OSXRemote/xbmchelper_main.mm b/tools/EventClients/Clients/OSXRemote/xbmchelper_main.mm new file mode 100644 index 0000000000..d4af31501a --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/xbmchelper_main.mm @@ -0,0 +1,210 @@ +#include "Carbon/Carbon.h" +#import "XBMCHelper.h" +#include <getopt.h> +#include <string> +#include <vector> +#include <sstream> +#include <fstream> +#include <iterator> + +using namespace std; + +//instantiate XBMCHelper which registers itself to IR handling stuff +XBMCHelper* gp_xbmchelper; +eRemoteMode g_mode = DEFAULT_MODE; +std::string g_server_address="localhost"; +std::string g_app_path = ""; +std::string g_app_home = ""; +double g_universal_timeout = 0.500; +bool g_verbose_mode = false; + +// +const char* PROGNAME="XBMCHelper"; +const char* PROGVERS="0.5"; + +void ParseOptions(int argc, char** argv); +void ReadConfig(); + +static struct option long_options[] = { +{ "help", no_argument, 0, 'h' }, +{ "server", required_argument, 0, 's' }, +{ "universal", no_argument, 0, 'u' }, +{ "multiremote", no_argument, 0, 'm' }, +{ "timeout", required_argument, 0, 't' }, +{ "verbose", no_argument, 0, 'v' }, +{ "externalConfig", no_argument, 0, 'x' }, +{ "appPath", required_argument, 0, 'a' }, +{ "appHome", required_argument, 0, 'z' }, +{ 0, 0, 0, 0 }, +}; + +static const char *options = "hs:umt:vxa:z:"; + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- +void usage(void) +{ + printf("%s (version %s)\n", PROGNAME, PROGVERS); + printf(" Sends Apple Remote events to XBMC.\n\n"); + printf("Usage: %s [OPTIONS...]\n\nOptions:\n", PROGNAME); + printf(" -h, --help print this help message and exit.\n"); + printf(" -s, --server <addr> send events to the specified IP.\n"); + printf(" -u, --universal runs in Universal Remote mode.\n"); + printf(" -t, --timeout <ms> timeout length for sequences (default: 500ms).\n"); + printf(" -m, --multiremote runs in Multi-Remote mode (adds remote identifier as additional idenfier to buttons)\n"); + printf(" -a, --appPath path to XBMC.app (MenuPress launch support).\n"); + printf(" -z, --appHome path to XBMC.app/Content/Resources/XBMX \n"); + printf(" -v, --verbose prints lots of debugging information.\n"); +} + +//---------------------------------------------------------------------------- +void ReadConfig() +{ + // Compute filename. + std::string strFile = getenv("HOME"); + strFile += "/Library/Application Support/XBMC/XBMCHelper.conf"; + + // Open file. + std::ifstream ifs(strFile.c_str()); + if (!ifs) + return; + + // Read file. + stringstream oss; + oss << ifs.rdbuf(); + + if (!ifs && !ifs.eof()) + return; + + // Tokenize. + string strData(oss.str()); + istringstream is(strData); + vector<string> args = vector<string>(istream_iterator<string>(is), istream_iterator<string>()); + + // Convert to char**. + int argc = args.size() + 1; + char** argv = new char*[argc + 1]; + int i = 0; + argv[i++] = "XBMCHelper"; + + for (vector<string>::iterator it = args.begin(); it != args.end(); ){ + //fixup the arguments, here: remove '"' like bash would normally do + std::string::size_type j = 0; + while ((j = it->find("\"", j)) != std::string::npos ) + it->replace(j, 1, ""); + argv[i++] = (char* )(*it++).c_str(); + } + + argv[i] = 0; + + // Parse the arguments. + ParseOptions(argc, argv); + + delete[] argv; +} + +//---------------------------------------------------------------------------- +void ParseOptions(int argc, char** argv) +{ + int c, option_index = 0; + //set the defaults + bool readExternal = false; + g_server_address = "localhost"; + g_mode = DEFAULT_MODE; + g_app_path = ""; + g_app_home = ""; + g_universal_timeout = 0.5; + g_verbose_mode = false; + + while ((c = getopt_long(argc, argv, options, long_options, &option_index)) != -1) + { + switch (c) { + case 'h': + usage(); + exit(0); + break; + case 'v': + g_verbose_mode = true; + break; + case 's': + g_server_address = optarg; + break; + case 'u': + g_mode = UNIVERSAL_MODE; + break; + case 'm': + g_mode = MULTIREMOTE_MODE; + break; + case 't': + g_universal_timeout = atof(optarg) * 0.001; + break; + case 'x': + readExternal = true; + break; + case 'a': + g_app_path = optarg; + break; + case 'z': + g_app_home = optarg; + break; + default: + usage(); + exit(1); + break; + } + } + //reset getopts state + optreset = 1; + optind = 0; + + if (readExternal == true) + ReadConfig(); + +} + +//---------------------------------------------------------------------------- +void StartHelper(){ + [gp_xbmchelper enableVerboseMode:g_verbose_mode]; + + //set apppath to startup when pressing Menu + [gp_xbmchelper setApplicationPath:[NSString stringWithCString:g_app_path.c_str()]]; + //set apppath to startup when pressing Menu + [gp_xbmchelper setApplicationHome:[NSString stringWithCString:g_app_home.c_str()]]; + //connect to specified server + [gp_xbmchelper connectToServer:[NSString stringWithCString:g_server_address.c_str()] withMode:g_mode withTimeout: g_universal_timeout]; +} + +//---------------------------------------------------------------------------- +void Reconfigure(int nSignal) +{ + if (nSignal == SIGHUP){ + ReadConfig(); + StartHelper(); + } + else { + QuitEventLoop(GetMainEventLoop()); + } +} + +//---------------------------------------------------------------------------- +int main (int argc, char * argv[]) { + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + NSLog(@"%s %s starting up...", PROGNAME, PROGVERS); + gp_xbmchelper = [[XBMCHelper alloc] init]; + + signal(SIGHUP, Reconfigure); + signal(SIGINT, Reconfigure); + signal(SIGTERM, Reconfigure); + + ParseOptions(argc,argv); + StartHelper(); + + //run event loop in this thread + RunCurrentEventLoop(kEventDurationForever); + NSLog(@"%s %s exiting...", PROGNAME, PROGVERS); + //cleanup + [gp_xbmchelper release]; + [pool drain]; + return 0; +} diff --git a/tools/EventClients/Clients/PS3 BD Remote/ps3_remote.py b/tools/EventClients/Clients/PS3 BD Remote/ps3_remote.py new file mode 100755 index 0000000000..c6b2aea4a1 --- /dev/null +++ b/tools/EventClients/Clients/PS3 BD Remote/ps3_remote.py @@ -0,0 +1,202 @@ +#!/usr/bin/python + +# This is a quick port of brandonj's PS3 remote script to use the event server +# for sending input events. +# +# The original script and documentation regarding the remote can be found at: +# http://xbmc.org/forum/showthread.php?t=28765 +# +# +# TODO: +# 1. Send keepalive ping at least once every 60 seconds to prevent timeouts +# 2. Permanent pairing +# 3. Detect if XBMC has been restarted (non trivial until broadcasting is +# implemented, until then maybe the HELO packet could be used instead of +# PING as keepalive +# + +import sys + +try: + # try loading modules from source directory + sys.path.append("../../lib/python") + from xbmcclient import * + from ps3.keymaps import keymap_remote as g_keymap # look here to change the keymapping + from bt.bt import * + ICON_PATH = "../../icons/" +except: + # fallback to system wide modules + from xbmc.xbmcclient import * + from xbmc.ps3.keymaps import keymap_remote as g_keymap # look here to change the keymapping + from xbmc.bt.bt import * + from xbmc.defs import * + +import os +import time + +xbmc = None +bticon = ICON_PATH + "/bluetooth.png" + +def get_remote_address(remote, target_name = "BD Remote Control"): + global xbmc + target_connected = False + target_address = None + while target_connected is False: + xbmc.send_notification("Action Required!", + "Hold Start+Enter on your remote.", + bticon) + print "Searching for %s" % target_name + print "(Hold Start + Enter on remote to make it discoverable)" + time.sleep(2) + + if not target_address: + try: + nearby_devices = bt_discover_devices() + except Exception, e: + print "Error performing bluetooth discovery" + print str(e) + xbmc.send_notification("Error", "Unable to find devices.", bticon) + time.sleep(5) + continue + + for bdaddr in nearby_devices: + bname = bt_lookup_name( bdaddr ) + addr = bt_lookup_addr ( bdaddr ) + print "%s (%s) in range" % (bname,addr) + if target_name == bname: + target_address = addr + break + + if target_address is not None: + print "Found %s with address %s" % (target_name, target_address) + xbmc.send_notification("Found Device", + "Pairing %s, please wait." % target_name, + bticon) + print "Attempting to pair with remote" + + try: + remote.connect((target_address,19)) + target_connected = True + print "Remote Paired.\a" + xbmc.send_notification("Pairing Successfull", + "Your remote was successfully "\ + "paired and is ready to be used.", + bticon) + except: + del remote + remote = bt_create_socket() + target_address = None + xbmc.send_notification("Pairing Failed", + "An error occurred while attempting to "\ + "pair.", bticon) + print "ERROR - Could Not Connect. Trying again..." + time.sleep(2) + else: + xbmc.send_notification("Error", "No remotes were found.", bticon) + print "Could not find BD Remote Control. Trying again..." + time.sleep(2) + return (remote,target_address) + + +def usage(): + print """ +PS3 Blu-Ray Remote Control Client for XBMC v0.1 + +Usage: ps3_remote.py <address> [port] + + address => address of system that XBMC is running on + ("localhost" if it is this machine) + + port => port to send packets to + (default 9777) +""" + +def process_keys(remote, xbmc): + """ + Return codes: + 0 - key was processed normally + 2 - socket read timeout + 3 - PS and then Skip Plus was pressed (sequentially) + 4 - PS and then Skip Minus was pressed (sequentially) + + FIXME: move to enums + """ + done = 0 + + try: + xbmc.previous_key + except: + xbmc.previous_key = "" + + xbmc.connect() + datalen = 0 + try: + data = remote.recv(1024) + datalen = len(data) + except Exception, e: + if str(e)=="timed out": + return 2 + time.sleep(2) + + # some other read exception occured, so raise it + raise e + + if datalen == 13: + keycode = data.encode("hex")[10:12] + if keycode == "ff": + xbmc.release_button() + return done + try: + # if the user presses the PS button followed by skip + or skip - + # return different codes. + if xbmc.previous_key == "43": + xbmc.previous_key = keycode + if keycode == "31": # skip + + return 3 + elif keycode == "30": # skip - + return 4 + + # save previous key press + xbmc.previous_key = keycode + + if g_keymap[keycode]: + xbmc.send_remote_button(g_keymap[keycode]) + except Exception, e: + print "Unknown data: %s" % str(e) + return done + +def main(): + global xbmc, bticon + + host = "127.0.0.1" + port = 9777 + + if len(sys.argv)>1: + try: + host = sys.argv[1] + port = sys.argv[2] + except: + pass + else: + return usage() + + loop_forever = True + xbmc = XBMCClient("PS3 Bluetooth Remote", + icon_file=bticon) + + while loop_forever is True: + target_connected = False + remote = bt_create_socket() + xbmc.connect(host, port) + (remote,target_address) = get_remote_address(remote) + while True: + if process_keys(remote, xbmc): + break + print "Disconnected." + try: + remote.close() + except: + print "Cannot close." + +if __name__=="__main__": + main() diff --git a/tools/EventClients/Clients/PS3 Sixaxis Controller/ps3d.py b/tools/EventClients/Clients/PS3 Sixaxis Controller/ps3d.py new file mode 100755 index 0000000000..b28f9ba9da --- /dev/null +++ b/tools/EventClients/Clients/PS3 Sixaxis Controller/ps3d.py @@ -0,0 +1,433 @@ +#!/usr/bin/python + +import sys +import traceback +import time +import struct +import threading + +sys.path.append("../PS3 BD Remote") + +try: + # try loading modules from source directory + sys.path.append("../../lib/python") + from bt.hid import HID + from bt.bt import bt_lookup_name + from xbmcclient import XBMCClient + from ps3 import sixaxis + from ps3.keymaps import keymap_sixaxis + from ps3_remote import process_keys as process_remote + try: + import zeroconf + except: + zeroconf = None + ICON_PATH = "../../icons/" +except: + # fallback to system wide modules + from xbmc.bt.hid import HID + from xbmc.bt.bt import bt_lookup_name + from xbmc.xbmcclient import XBMCClient + from xbmc.ps3 import sixaxis + from xbmc.ps3.keymaps import keymap_sixaxis + from xbmc.ps3_remote import process_keys as process_remote + from xbmc.defs import * + try: + import xbmc.zeroconf as zeroconf + except: + zeroconf = None + + +event_threads = [] + +def printerr(): + trace = "" + exception = "" + exc_list = traceback.format_exception_only (sys.exc_type, sys.exc_value) + for entry in exc_list: + exception += entry + tb_list = traceback.format_tb(sys.exc_info()[2]) + for entry in tb_list: + trace += entry + print("%s\n%s" % (exception, trace), "Script Error") + + +class StoppableThread ( threading.Thread ): + def __init__(self): + threading.Thread.__init__(self) + self._stop = False + self.set_timeout(0) + + def stop_thread(self): + self._stop = True + + def stop(self): + return self._stop + + def close_sockets(self): + if self.isock: + try: + self.isock.close() + except: + pass + self.isock = None + if self.csock: + try: + self.csock.close() + except: + pass + self.csock = None + self.last_action = 0 + + def set_timeout(self, seconds): + self.timeout = seconds + + def reset_timeout(self): + self.last_action = time.time() + + def idle_time(self): + return time.time() - self.last_action + + def timed_out(self): + if (time.time() - self.last_action) > self.timeout: + return True + else: + return False + +# to make sure all combination keys are checked first +# we sort the keymap's button codes in reverse order +# this guranties that any bit combined button code +# will be processed first +keymap_sixaxis_keys = keymap_sixaxis.keys() +keymap_sixaxis_keys.sort() +keymap_sixaxis_keys.reverse() + +def getkeys(bflags): + keys = []; + for k in keymap_sixaxis_keys: + if (k & bflags) == k: + keys.append(k) + bflags = bflags & ~k + return keys; + +class PS3SixaxisThread ( StoppableThread ): + def __init__(self, csock, isock, ipaddr="127.0.0.1"): + StoppableThread.__init__(self) + self.csock = csock + self.isock = isock + self.xbmc = XBMCClient(name="PS3 Sixaxis", icon_file=ICON_PATH + "/bluetooth.png", ip=ipaddr) + self.set_timeout(600) + + def run(self): + sixaxis.initialize(self.csock, self.isock) + self.xbmc.connect() + bflags = 0 + released = set() + pressed = set() + pending = set() + held = set() + psflags = 0 + psdown = 0 + toggle_mouse = 0 + self.reset_timeout() + try: + while not self.stop(): + if self.timed_out(): + + for key in (held | pressed): + (mapname, action, amount, axis) = keymap_sixaxis[key] + self.xbmc.send_button_state(map=mapname, button=action, amount=0, down=0, axis=axis) + + raise Exception("PS3 Sixaxis powering off, timed out") + if self.idle_time() > 50: + self.xbmc.connect() + try: + data = sixaxis.read_input(self.isock) + except Exception, e: + print str(e) + break + if not data: + continue + + (bflags, psflags, pressure) = sixaxis.process_input(data, self.xbmc, toggle_mouse) + + if psflags: + self.reset_timeout() + if psdown: + if (time.time() - psdown) > 5: + + for key in (held | pressed): + (mapname, action, amount, axis) = keymap_sixaxis[key] + self.xbmc.send_button_state(map=mapname, button=action, amount=0, down=0, axis=axis) + + raise Exception("PS3 Sixaxis powering off, user request") + else: + psdown = time.time() + else: + if psdown: + toggle_mouse = 1 - toggle_mouse + psdown = 0 + + keys = set(getkeys(bflags)) + released = (pressed | held) - keys + held = (pressed | held) - released + pressed = (keys - held) & pending + pending = (keys - held) + + for key in released: + (mapname, action, amount, axis) = keymap_sixaxis[key] + self.xbmc.send_button_state(map=mapname, button=action, amount=0, down=0, axis=axis) + + for key in held: + (mapname, action, amount, axis) = keymap_sixaxis[key] + if amount > 0: + amount = pressure[amount-1] * 256 + self.xbmc.send_button_state(map=mapname, button=action, amount=amount, down=1, axis=axis) + + for key in pressed: + (mapname, action, amount, axis) = keymap_sixaxis[key] + if amount > 0: + amount = pressure[amount-1] * 256 + self.xbmc.send_button_state(map=mapname, button=action, amount=amount, down=1, axis=axis) + + if keys: + self.reset_timeout() + + + except Exception, e: + printerr() + self.close_sockets() + + +class PS3RemoteThread ( StoppableThread ): + def __init__(self, csock, isock, ipaddr="127.0.0.1"): + StoppableThread.__init__(self) + self.csock = csock + self.isock = isock + self.xbmc = XBMCClient(name="PS3 Blu-Ray Remote", icon_file=ICON_PATH + "/bluetooth.png", ip=ipaddr) + self.set_timeout(600) + self.services = [] + self.current_xbmc = 0 + + def run(self): + sixaxis.initialize(self.csock, self.isock) + self.xbmc.connect() + try: + # start the zeroconf thread if possible + try: + self.zeroconf_thread = ZeroconfThread() + self.zeroconf_thread.add_service('_xbmc-events._udp', + self.zeroconf_service_handler) + self.zeroconf_thread.start() + except Exception, e: + print str(e) + + # main thread loop + while not self.stop(): + status = process_remote(self.isock, self.xbmc) + + if status == 2: # 2 = socket read timeout + if self.timed_out(): + raise Exception("PS3 Blu-Ray Remote powering off, "\ + "timed out") + elif status == 3: # 3 = ps and skip + + self.next_xbmc() + + elif status == 4: # 4 = ps and skip - + self.previous_xbmc() + + elif not status: # 0 = keys are normally processed + self.reset_timeout() + + # process_remote() will raise an exception on read errors + except Exception, e: + print str(e) + + self.zeroconf_thread.stop() + self.close_sockets() + + def next_xbmc(self): + """ + Connect to the next XBMC instance + """ + self.current_xbmc = (self.current_xbmc + 1) % len( self.services ) + self.reconnect() + return + + def previous_xbmc(self): + """ + Connect to the previous XBMC instance + """ + self.current_xbmc -= 1 + if self.current_xbmc < 0 : + self.current_xbmc = len( self.services ) - 1 + self.reconnect() + return + + def reconnect(self): + """ + Reconnect to an XBMC instance based on self.current_xbmc + """ + try: + service = self.services[ self.current_xbmc ] + print "Connecting to %s" % service['name'] + self.xbmc.connect( service['address'], service['port'] ) + self.xbmc.send_notification("PS3 Blu-Ray Remote", "New Connection", None) + except Exception, e: + print str(e) + + def zeroconf_service_handler(self, event, service): + """ + Zeroconf event handler + """ + if event == zeroconf.SERVICE_FOUND: # new xbmc service detected + self.services.append( service ) + + elif event == zeroconf.SERVICE_LOST: # xbmc service lost + try: + # search for the service by name, since IP+port isn't available + for s in self.services: + # nuke it, if found + if service['name'] == s['name']: + self.services.remove(s) + break + except: + pass + return + + +class ZeroconfThread ( threading.Thread ): + """ + + """ + def __init__(self): + threading.Thread.__init__(self) + self._zbrowser = None + self._services = [] + + def run(self): + if zeroconf: + # create zeroconf service browser + self._zbrowser = zeroconf.Browser() + + # add the requested services + for service in self._services: + self._zbrowser.add_service( service[0], service[1] ) + + # run the event loop + self._zbrowser.run() + + return + + + def stop(self): + """ + Stop the zeroconf browser + """ + try: + self._zbrowser.stop() + except: + pass + return + + def add_service(self, type, handler): + """ + Add a new service to search for. + NOTE: Services must be added before thread starts. + """ + self._services.append( [ type, handler ] ) + + +def usage(): + print """ +PS3 Sixaxis / Blu-Ray Remote HID Server v0.1 + +Usage: ps3.py [bdaddress] [XBMC host] + + bdaddress => address of local bluetooth device to use (default: auto) + (e.g. aa:bb:cc:dd:ee:ff) + ip address => IP address or hostname of the XBMC instance (default: localhost) + (e.g. 192.168.1.110) +""" + +def start_hidd(bdaddr=None, ipaddr="127.0.0.1"): + devices = [ 'PLAYSTATION(R)3 Controller', + 'BD Remote Control' ] + hid = HID(bdaddr) + while True: + if hid.listen(): + (csock, addr) = hid.get_control_socket() + device_name = bt_lookup_name(addr[0]) + if device_name == devices[0]: + # handle PS3 controller + handle_ps3_controller(hid, ipaddr) + elif device_name == devices[1]: + # handle the PS3 remote + handle_ps3_remote(hid, ipaddr) + else: + print "Unknown Device: %s" % (device_name) + +def handle_ps3_controller(hid, ipaddr): + print "Received connection from a Sixaxis PS3 Controller" + csock = hid.get_control_socket()[0] + isock = hid.get_interrupt_socket()[0] + sixaxis = PS3SixaxisThread(csock, isock, ipaddr) + add_thread(sixaxis) + sixaxis.start() + return + +def handle_ps3_remote(hid, ipaddr): + print "Received connection from a PS3 Blu-Ray Remote" + csock = hid.get_control_socket()[0] + isock = hid.get_interrupt_socket()[0] + isock.settimeout(1) + remote = PS3RemoteThread(csock, isock, ipaddr) + add_thread(remote) + remote.start() + return + +def add_thread(thread): + global event_threads + event_threads.append(thread) + +def main(): + if len(sys.argv)>3: + return usage() + bdaddr = "" + ipaddr = "127.0.0.1" + try: + for addr in sys.argv[1:]: + try: + # ensure that the addr is of the format 'aa:bb:cc:dd:ee:ff' + if "".join([ str(len(a)) for a in addr.split(":") ]) != "222222": + raise Exception("Invalid format") + bdaddr = addr + print "Connecting to Bluetooth device: %s" % bdaddr + except Exception, e: + try: + ipaddr = addr + print "Connecting to : %s" % ipaddr + except: + print str(e) + return usage() + except Exception, e: + pass + + print "Starting HID daemon" + start_hidd(bdaddr, ipaddr) + +if __name__=="__main__": + try: + main() + finally: + for t in event_threads: + try: + print "Waiting for thread "+str(t)+" to terminate" + t.stop_thread() + if t.isAlive(): + t.join() + print "Thread "+str(t)+" terminated" + + except Exception, e: + print str(e) + pass + diff --git a/tools/EventClients/Clients/WiiRemote/CWIID_WiiRemote.cpp b/tools/EventClients/Clients/WiiRemote/CWIID_WiiRemote.cpp new file mode 100644 index 0000000000..08ba318f86 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/CWIID_WiiRemote.cpp @@ -0,0 +1,774 @@ +/*************************************************************************** + * Copyright (C) 2007 by Tobias Arrskog,,, * + * topfs@tobias * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +// Compiles with g++ WiiRemote.cpp -lcwiid -o WiiRemote +// Preferably with libcwiid >= 6.0 + +#include "CWIID_WiiRemote.h" + + +bool g_AllowReconnect = true; +bool g_AllowMouse = true; +bool g_AllowNunchuck = true; + +CPacketHELO *g_Ping = NULL; + +#ifndef ICON_PATH +#define ICON_PATH "../../" +#endif +std::string g_BluetoothIconPath = std::string(ICON_PATH) + std::string("/bluetooth.png"); + +long getTicks() +{ + long ticks; + struct timeval now; + gettimeofday(&now, NULL); + ticks = now.tv_sec * 1000l; + ticks += now.tv_usec / 1000l; + return ticks; +} + +CWiiRemote g_WiiRemote; + +#ifdef CWIID_OLD +void CWiiRemote::MessageCallback(cwiid_wiimote_t *wiiremote, int mesg_count, union cwiid_mesg mesg[]) +{ + MessageCallback(wiiremote, mesg_count, mesg, NULL); +} +#endif + +/* The MessageCallback for the Wiiremote. + This callback is used for error reports, mainly to see if the connection has been broken + This callback is also used for getting the IR sources, if this is done in update as with buttons we usually only get 1 IR source at a time wich is much harder to calculate */ +void CWiiRemote::MessageCallback(cwiid_wiimote_t *wiiremote, int mesg_count, union cwiid_mesg mesg[], struct timespec *timestamp) +{ + for (int i=0; i < mesg_count; i++) + { + int valid_source; + switch (mesg[i].type) + { + case CWIID_MESG_IR: + valid_source = 0; + for (int j = 0; j < CWIID_IR_SRC_COUNT; j++) + { + if (mesg[i].ir_mesg.src[j].valid) + valid_source++; + } + if (valid_source == 2) + { + g_WiiRemote.CalculateMousePointer(mesg[i].ir_mesg.src[0].pos[CWIID_X], + mesg[i].ir_mesg.src[0].pos[CWIID_Y], + mesg[i].ir_mesg.src[1].pos[CWIID_X], + mesg[i].ir_mesg.src[1].pos[CWIID_Y]); + } + else if (valid_source > 2) + { //TODO Make this care with the strenght of the sources + g_WiiRemote.CalculateMousePointer(mesg[i].ir_mesg.src[0].pos[CWIID_X], + mesg[i].ir_mesg.src[0].pos[CWIID_Y], + mesg[i].ir_mesg.src[1].pos[CWIID_X], + mesg[i].ir_mesg.src[1].pos[CWIID_Y]); + } + break; + case CWIID_MESG_ERROR: + g_WiiRemote.DisconnectNow(true); + break; + case CWIID_MESG_BTN: + g_WiiRemote.ProcessKey(mesg[i].btn_mesg.buttons); + break; + case CWIID_MESG_STATUS: + //Here we can figure out Extensiontypes and such + break; + case CWIID_MESG_NUNCHUK: + g_WiiRemote.ProcessNunchuck(mesg[i].nunchuk_mesg); + break; + case CWIID_MESG_CLASSIC: + //Not implemented + break; + case CWIID_MESG_ACC: + //Not implemented + break; + case CWIID_MESG_UNKNOWN: + //... + break; + } + } +#ifdef CWIID_OLD + g_WiiRemote.CheckIn(); +#endif +} + +#ifndef _DEBUG +/* This takes the errors generated at pre-connect and silence them as they are mostly not needed */ +void CWiiRemote::ErrorCallback(struct wiimote *wiiremote, const char *str, va_list ap) +{ + //Error checking +} +#endif + +//Constructor +/*This constructor is never used but it shows how one would connect to just a specific Wiiremote by Mac-Adress*/ +CWiiRemote::CWiiRemote(char *wii_btaddr) +{ + SetBluetoothAddress(wii_btaddr); + m_SamplesX = NULL; + m_SamplesY = NULL; + + m_JoyMap = NULL; +} + +//Destructor +CWiiRemote::~CWiiRemote() +{ + if (m_connected == true) + this->DisconnectNow(false); + + if (m_SamplesY != NULL) + free(m_SamplesY); + if (m_SamplesX != NULL) + free(m_SamplesX); + + if (m_JoyMap) + free(m_JoyMap); +} + +//---------------------Public------------------------------------------------------------------- +/* Basicly this just sets up standard control bits */ +void CWiiRemote::SetBluetoothAddress(const char *btaddr) +{ + if (btaddr != NULL) + str2ba(btaddr, &m_btaddr); + else + bacpy(&m_btaddr, &(*BDADDR_ANY)); +} + +void CWiiRemote::SetSensativity(float DeadX, float DeadY, int NumSamples) +{ + m_NumSamples = NumSamples; + + m_MinX = MOUSE_MAX * DeadX; + m_MinY = MOUSE_MAX * DeadY; + + m_MaxX = MOUSE_MAX * (1.0f + DeadX + DeadX); + m_MaxY = MOUSE_MAX * (1.0f + DeadY + DeadY); + + if (m_SamplesY != NULL) + delete [] m_SamplesY; + if (m_SamplesX != NULL) + delete [] m_SamplesX; + + m_SamplesY = new int[m_NumSamples]; + m_SamplesX = new int[m_NumSamples]; +} + +void CWiiRemote::SetJoystickMap(const char *JoyMap) +{ + if (m_JoyMap) + free(m_JoyMap); + if (JoyMap != NULL) + { + m_JoyMap = new char[strlen(JoyMap) + 4]; + sprintf(m_JoyMap, "JS0:%s", JoyMap); + } + else + { + m_JoyMap = new char[strlen("JS0:WiiRemote")]; + strcpy(m_JoyMap, "JS0:WiiRemote"); + } +} + +void CWiiRemote::Initialize(CAddress Addr, int Socket) +{ + m_connected = false; + m_lastKeyPressed = 0; + m_LastKey = 0; + m_buttonRepeat = false; + m_lastKeyPressedNunchuck = 0; + m_LastKeyNunchuck = 0; + m_buttonRepeatNunchuck = false; + m_useIRMouse = true; + m_rptMode = 0; + + m_Socket = Socket; + m_MyAddr = Addr; + + m_NumSamples = WIIREMOTE_SAMPLES; + + m_MaxX = WIIREMOTE_X_MAX; + m_MaxY = WIIREMOTE_Y_MAX; + m_MinX = WIIREMOTE_X_MIN; + m_MinY = WIIREMOTE_Y_MIN; +#ifdef CWIID_OLD + m_LastMsgTime = getTicks(); +#endif + + //All control bits are set to false when cwiid is started + //Report Button presses + ToggleBit(m_rptMode, CWIID_RPT_BTN); + if (g_AllowNunchuck) + ToggleBit(m_rptMode, CWIID_RPT_NUNCHUK); + + //If wiiremote is used as a mouse, then report the IR sources +#ifndef CWIID_OLD + if (m_useIRMouse) +#endif + ToggleBit(m_rptMode, CWIID_RPT_IR); + + //Have the first and fourth LED on the Wiiremote shine when connected + ToggleBit(m_ledState, CWIID_LED1_ON); + ToggleBit(m_ledState, CWIID_LED4_ON); +} + +/* Update is run regulary and we gather the state of the Wiiremote and see if the user have pressed on a button or moved the wiiremote + This could have been done with callbacks instead but it doesn't look nice in C++*/ +void CWiiRemote::Update() +{ + if (m_DisconnectWhenPossible) + {//If the user have choosen to disconnect or lost comunication + DisconnectNow(true); + m_DisconnectWhenPossible = false; + } +#ifdef CWIID_OLD + if(m_connected) + {//Here we check if the connection is suddenly broken + if (!CheckConnection()) + { + DisconnectNow(true); + return; + } + } +#endif +} + +/* Enable mouse emulation */ +void CWiiRemote::EnableMouseEmulation() +{ + if (m_useIRMouse) + return; + + m_useIRMouse = true; + +#ifndef CWIID_OLD + //We toggle IR Reporting (Save resources?) + if (!(m_rptMode & CWIID_RPT_IR)) + ToggleBit(m_rptMode, CWIID_RPT_IR); + if (m_connected) + SetRptMode(); +#endif + + CPacketLOG log(LOGDEBUG, "Enabled WiiRemote mouse emulation"); + log.Send(m_Socket, m_MyAddr); +} +/* Disable mouse emulation */ +void CWiiRemote::DisableMouseEmulation() +{ + if (!m_useIRMouse) + return; + + m_useIRMouse = false; +#ifndef CWIID_OLD + //We toggle IR Reporting (Save resources?) + if (m_rptMode & CWIID_RPT_IR) + ToggleBit(m_rptMode, CWIID_RPT_IR); + if (m_connected) + SetRptMode(); +#endif + + CPacketLOG log(LOGDEBUG, "Disabled WiiRemote mouse emulation"); + log.Send(m_Socket, m_MyAddr); +} + +/* Is a wiiremote connected*/ +bool CWiiRemote::GetConnected() +{ + return m_connected; +} + +/* Disconnect ASAP*/ +void CWiiRemote::Disconnect() +{ //This is always called from a criticalsection + if (m_connected) + m_DisconnectWhenPossible = true; +} + +#ifdef CWIID_OLD +/* This function is mostly a hack as CWIID < 6.0 doesn't report on disconnects, this function is called everytime + a message is sent to the callback (Will be once every 10 ms or so) this is to see if the connection is interupted. */ +void CWiiRemote::CheckIn() +{ //This is always called from a criticalsection + m_LastMsgTime = getTicks(); +} +#endif + +//---------------------Private------------------------------------------------------------------- +/* Connect is designed to be run in a different thread as it only + exits if wiiremote is either disabled or a connection is made*/ +bool CWiiRemote::Connect() +{ +#ifndef _DEBUG + cwiid_set_err(ErrorCallback); +#endif + while (!m_connected) + { + g_Ping->Send(m_Socket, m_MyAddr); + int flags = 0; + ToggleBit(flags, CWIID_FLAG_MESG_IFC); + ToggleBit(flags, CWIID_FLAG_REPEAT_BTN); + + m_wiiremoteHandle = cwiid_connect(&m_btaddr, flags); + if (m_wiiremoteHandle != NULL) + { + SetupWiiRemote(); + // get battery state etc. + cwiid_state wiiremote_state; + int err = cwiid_get_state(m_wiiremoteHandle, &wiiremote_state); + if (!err) + { + char Mesg[1024]; + sprintf(Mesg, "%i%% battery remaining", static_cast<int>(((float)(wiiremote_state.battery)/CWIID_BATTERY_MAX)*100.0)); + CPacketNOTIFICATION notification("Wii Remote connected", Mesg, ICON_PNG, g_BluetoothIconPath.c_str()); + notification.Send(m_Socket, m_MyAddr); + } + else + { + printf("Problem probing for status of WiiRemote; cwiid_get_state returned non-zero\n"); + CPacketLOG log(LOGNOTICE, "Problem probing for status of WiiRemote; cwiid_get_state returned non-zero"); + log.Send(m_Socket, m_MyAddr); + CPacketNOTIFICATION notification("Wii Remote connected", "", ICON_PNG, g_BluetoothIconPath.c_str()); + notification.Send(m_Socket, m_MyAddr); + } +#ifdef CWIID_OLD + /* CheckIn to say that this is the last msg, If this isn't called it could give issues if we Connects -> Disconnect and then try to connect again + the CWIID_OLD hack would automaticly disconnect the wiiremote as the lastmsg is too old. */ + CheckIn(); +#endif + m_connected = true; + + CPacketLOG log(LOGNOTICE, "Sucessfully connected a WiiRemote"); + log.Send(m_Socket, m_MyAddr); + return true; + } + //Here's a good place to have a quit flag check... + + } + return false; +} + +/* Disconnect */ +void CWiiRemote::DisconnectNow(bool startConnectThread) +{ + if (m_connected) //It shouldn't be enabled at the same time as it is connected + { + cwiid_disconnect(m_wiiremoteHandle); + + if (g_AllowReconnect) + { + CPacketNOTIFICATION notification("Wii Remote disconnected", "Press 1 and 2 to reconnect", ICON_PNG, g_BluetoothIconPath.c_str()); + notification.Send(m_Socket, m_MyAddr); + } + else + { + CPacketNOTIFICATION notification("Wii Remote disconnected", "", ICON_PNG, g_BluetoothIconPath.c_str()); + notification.Send(m_Socket, m_MyAddr); + } + + CPacketLOG log(LOGNOTICE, "Sucessfully disconnected a WiiRemote"); + log.Send(m_Socket, m_MyAddr); + } + m_connected = false; +} + +#ifdef CWIID_OLD +/* This is a harsh check if there really is a connection, It will mainly be used in CWIID < 6.0 + as it doesn't report connect error, wich is needed to see if the Wiiremote suddenly disconnected. + This could possible be done with bluetooth specific queries but I cannot find how to do it. */ +bool CWiiRemote::CheckConnection() +{ + if ((getTicks() - m_LastMsgTime) > 1000) + { + CPacketLOG log(LOGNOTICE, "Lost connection to the WiiRemote"); + log.Send(m_Socket, m_MyAddr); + return false; + } + else + return true; +} +#endif + +/* Sets rpt mode when a new wiiremote is connected */ +void CWiiRemote::SetupWiiRemote() +{ //Lights up the apropriate led and setups the rapport mode, so buttons and IR work + SetRptMode(); + SetLedState(); + + for (int i = 0; i < WIIREMOTE_SAMPLES; i++) + { + m_SamplesX[i] = 0; + m_SamplesY[i] = 0; + } + + if (cwiid_set_mesg_callback(m_wiiremoteHandle, MessageCallback)) + { + CPacketLOG log(LOGERROR, "Unable to set message callback to the WiiRemote"); + log.Send(m_Socket, m_MyAddr); + } +} + +void CWiiRemote::ProcessKey(int Key) +{ + if (Key != m_LastKey) + { + m_LastKey = Key; + m_lastKeyPressed = getTicks(); + m_buttonRepeat = false; + } + else + { + if (m_buttonRepeat) + { + if (getTicks() - m_lastKeyPressed > WIIREMOTE_BUTTON_REPEAT_TIME) + m_lastKeyPressed = getTicks(); + else + return; + } + else + { + if (getTicks() - m_lastKeyPressed > WIIREMOTE_BUTTON_DELAY_TIME) + { + m_buttonRepeat = true; + m_lastKeyPressed = getTicks(); + } + else + return; + } + } + + int RtnKey = -1; + + if (Key == CWIID_BTN_UP) + RtnKey = 1; + else if (Key == CWIID_BTN_RIGHT) + RtnKey = 4; + else if (Key == CWIID_BTN_LEFT) + RtnKey = 3; + else if (Key == CWIID_BTN_DOWN) + RtnKey = 2; + + else if (Key == CWIID_BTN_A) + RtnKey = 5; + else if (Key == CWIID_BTN_B) + RtnKey = 6; + + else if (Key == CWIID_BTN_MINUS) + RtnKey = 7; + else if (Key == CWIID_BTN_PLUS) + RtnKey = 9; + + else if (Key == CWIID_BTN_HOME) + RtnKey = 8; + + else if (Key == CWIID_BTN_1) + RtnKey = 10; + else if (Key == CWIID_BTN_2) + RtnKey = 11; + + if (RtnKey != -1) + { + CPacketBUTTON btn(RtnKey, m_JoyMap, BTN_QUEUE | BTN_NO_REPEAT); + btn.Send(m_Socket, m_MyAddr); + } +} + +void CWiiRemote::ProcessNunchuck(struct cwiid_nunchuk_mesg &Nunchuck) +{ + if (Nunchuck.stick[0] > 135) + { //R + int x = (int)((((float)Nunchuck.stick[0] - 135.0f) / 95.0f) * 65535.0f); + printf("Right: %i\n", x); + CPacketBUTTON btn(24, m_JoyMap, (BTN_QUEUE | BTN_DOWN), x); + btn.Send(m_Socket, m_MyAddr); + } + else if (Nunchuck.stick[0] < 125) + { //L + int x = (int)((((float)Nunchuck.stick[0] - 125.0f) / 90.0f) * -65535.0f); + printf("Left: %i\n", x); + CPacketBUTTON btn(23, m_JoyMap, (BTN_QUEUE | BTN_DOWN), x); + btn.Send(m_Socket, m_MyAddr); + } + + if (Nunchuck.stick[1] > 130) + { //U + int x = (int)((((float)Nunchuck.stick[1] - 130.0f) / 92.0f) * 65535.0f); + printf("Up: %i\n", x); + CPacketBUTTON btn(21, m_JoyMap, (BTN_QUEUE | BTN_DOWN), x); + btn.Send(m_Socket, m_MyAddr); + } + else if (Nunchuck.stick[1] < 120) + { //D + int x = (int)((((float)Nunchuck.stick[1] - 120.0f) / 90.0f) * -65535.0f); + printf("Down: %i\n", x); + CPacketBUTTON btn(22, m_JoyMap, (BTN_QUEUE | BTN_DOWN), x); + btn.Send(m_Socket, m_MyAddr); + } + + if (Nunchuck.buttons != m_LastKeyNunchuck) + { + m_LastKeyNunchuck = Nunchuck.buttons; + m_lastKeyPressedNunchuck = getTicks(); + m_buttonRepeatNunchuck = false; + } + else + { + if (m_buttonRepeatNunchuck) + { + if (getTicks() - m_lastKeyPressedNunchuck > WIIREMOTE_BUTTON_REPEAT_TIME) + m_lastKeyPressedNunchuck = getTicks(); + else + return; + } + else + { + if (getTicks() - m_lastKeyPressedNunchuck > WIIREMOTE_BUTTON_DELAY_TIME) + { + m_buttonRepeatNunchuck = true; + m_lastKeyPressedNunchuck = getTicks(); + } + else + return; + } + } + + int RtnKey = -1; + + if (Nunchuck.buttons == CWIID_NUNCHUK_BTN_C) + RtnKey = 25; + else if (Nunchuck.buttons == CWIID_NUNCHUK_BTN_Z) + RtnKey = 26; + + if (RtnKey != -1) + { + CPacketBUTTON btn(RtnKey, m_JoyMap, BTN_QUEUE | BTN_NO_REPEAT); + btn.Send(m_Socket, m_MyAddr); + } +} + +/* Tell cwiid wich data will be reported */ +void CWiiRemote::SetRptMode() +{ //Sets our wiiremote to report something, for example IR, Buttons +#ifdef CWIID_OLD + if (cwiid_command(m_wiiremoteHandle, CWIID_CMD_RPT_MODE, m_rptMode)) +#else + if (cwiid_set_rpt_mode(m_wiiremoteHandle, m_rptMode)) +#endif + { + CPacketLOG log(LOGERROR, "Error setting WiiRemote report mode"); + log.Send(m_Socket, m_MyAddr); + } +} +/* Tell cwiid the LED states */ +void CWiiRemote::SetLedState() +{ //Sets our leds on the wiiremote +#ifdef CWIID_OLD + if (cwiid_command(m_wiiremoteHandle, CWIID_CMD_LED, m_ledState)) +#else + if (cwiid_set_led(m_wiiremoteHandle, m_ledState)) +#endif + { + CPacketLOG log(LOGERROR, "Error setting WiiRemote LED state"); + log.Send(m_Socket, m_MyAddr); + } +} + +/* Calculate the mousepointer from 2 IR sources (Default) */ +void CWiiRemote::CalculateMousePointer(int x1, int y1, int x2, int y2) +{ + int x3, y3; + + x3 = ( (x1 + x2) / 2 ); + y3 = ( (y1 + y2) / 2 ); + + x3 = (int)( ((float)x3 / (float)CWIID_IR_X_MAX) * m_MaxX); + y3 = (int)( ((float)y3 / (float)CWIID_IR_Y_MAX) * m_MaxY); + + x3 = (int)(x3 - m_MinX); + y3 = (int)(y3 - m_MinY); + + if (x3 < MOUSE_MIN) x3 = MOUSE_MIN; + else if (x3 > MOUSE_MAX) x3 = MOUSE_MAX; + + if (y3 < MOUSE_MIN) y3 = MOUSE_MIN; + else if (y3 > MOUSE_MAX) y3 = MOUSE_MAX; + + x3 = MOUSE_MAX - x3; + + if (m_NumSamples == 1) + { + CPacketMOUSE mouse(x3, y3); + mouse.Send(m_Socket, m_MyAddr); + return; + } + else + { + for (int i = m_NumSamples; i > 0; i--) + { + m_SamplesX[i] = m_SamplesX[i-1]; + m_SamplesY[i] = m_SamplesY[i-1]; + } + + m_SamplesX[0] = x3; + m_SamplesY[0] = y3; + + long x4 = 0, y4 = 0; + + for (int i = 0; i < m_NumSamples; i++) + { + x4 += m_SamplesX[i]; + y4 += m_SamplesY[i]; + } + CPacketMOUSE mouse((x4 / m_NumSamples), (y4 / m_NumSamples)); + mouse.Send(m_Socket, m_MyAddr); + } +} + + + + + + + + + + + + + + + + +void PrintHelp(const char *Prog) +{ + printf("Commands:\n"); + printf("\t--disable-mouseemulation\n\t--disable-reconnect\n\t--disable-nunchuck\n"); + printf("\t--address ADDRESS\n\t--port PORT\n"); + printf("\t--btaddr MACADDRESS\n"); + printf("\t--deadzone-x DEADX | Number between 0 - 100 (Default: %i)\n", (int)(DEADZONE_X * 100)); + printf("\t--deadzone-y DEADY | Number between 0 - 100 (Default: %i)\n", (int)(DEADZONE_Y * 100)); + printf("\t--deadzone DEAD | Sets both X and Y too the number\n"); + printf("\t--smoothing-samples SAMPLE | Number 1 counts as Off (Default: %i)\n", WIIREMOTE_SAMPLES); + printf("\t--joystick-map JOYMAP | The string ID for the joymap (Default: WiiRemote)\n"); +} + +int main(int argc, char **argv) +{ + char *Address = NULL; + char *btaddr = NULL; + int Port = 9777; + + int NumSamples = WIIREMOTE_SAMPLES; + float DeadX = DEADZONE_X; + float DeadY = DEADZONE_Y; + + char *JoyMap = NULL; + + for (int i = 0; i < argc; i++) + { + if (strcmp(argv[i], "--help") == 0) + { + PrintHelp(argv[0]); + return 0; + } + else if (strcmp(argv[i], "--disable-mouseemulation") == 0) + g_AllowMouse = false; + else if (strcmp(argv[i], "--disable-reconnect") == 0) + g_AllowReconnect = false; + else if (strcmp(argv[i], "--disable-nunchuck") == 0) + g_AllowNunchuck = false; + else if (strcmp(argv[i], "--address") == 0 && ((i + 1) <= argc)) + Address = argv[i + 1]; + else if (strcmp(argv[i], "--port") == 0 && ((i + 1) <= argc)) + Port = atoi(argv[i + 1]); + else if (strcmp(argv[i], "--btaddr") == 0 && ((i + 1) <= argc)) + btaddr = argv[i + 1]; + else if (strcmp(argv[i], "--deadzone-x") == 0 && ((i + 1) <= argc)) + DeadX = ((float)atoi(argv[i + 1]) / 100.0f); + else if (strcmp(argv[i], "--deadzone-y") == 0 && ((i + 1) <= argc)) + DeadY = ((float)atoi(argv[i + 1]) / 100.0f); + else if (strcmp(argv[i], "--deadzone") == 0 && ((i + 1) <= argc)) + DeadX = DeadY = ((float)atoi(argv[i + 1]) / 100.0f); + else if (strcmp(argv[i], "--smoothing-samples") == 0 && ((i + 1) <= argc)) + NumSamples = atoi(argv[i + 1]); + else if (strcmp(argv[i], "--joystick-map") == 0 && ((i + 1) <= argc)) + JoyMap = argv[i + 1]; + } + + if (NumSamples < 1 || DeadX < 0 || DeadY < 0 || DeadX > 1 || DeadY > 1) + { + PrintHelp(argv[0]); + return -1; + } + + CAddress my_addr(Address, Port); // Address => localhost on 9777 + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) + { + printf("Error creating socket\n"); + return -1; + } + + if (hci_get_route(NULL) < 0) + { + CPacketLOG log(LOGERROR, "Error No bluetooth device"); + log.Send(sockfd, my_addr); + return -1; + } + g_Ping = new CPacketHELO("WiiRemote", ICON_PNG, g_BluetoothIconPath.c_str()); + g_WiiRemote.Initialize(my_addr, sockfd); + g_WiiRemote.SetBluetoothAddress(btaddr); + g_WiiRemote.SetSensativity(DeadX, DeadY, NumSamples); + g_WiiRemote.SetSensativity(DeadX, DeadY, NumSamples); + g_WiiRemote.SetJoystickMap(JoyMap); + if (g_AllowMouse) + g_WiiRemote.EnableMouseEmulation(); + else + g_WiiRemote.DisableMouseEmulation(); + + g_Ping->Send(sockfd, my_addr); + bool HaveConnected = false; + while (true) + { + bool Connected = g_WiiRemote.GetConnected(); + + while (!Connected) + { + if (HaveConnected && !g_AllowReconnect) + exit(0); + + Connected = g_WiiRemote.Connect(); + HaveConnected = true; + } +#ifdef CWIID_OLD +// Update the state of the WiiRemote more often when we have the old lib due too it not telling when disconnected.. + sleep (5); +#else + sleep (15); +#endif + g_Ping->Send(sockfd, my_addr); + g_WiiRemote.Update(); + } +} diff --git a/tools/EventClients/Clients/WiiRemote/CWIID_WiiRemote.h b/tools/EventClients/Clients/WiiRemote/CWIID_WiiRemote.h new file mode 100644 index 0000000000..87e9010e6f --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/CWIID_WiiRemote.h @@ -0,0 +1,170 @@ +/*************************************************************************** + * Copyright (C) 2007 by Tobias Arrskog,,, * + * topfs@tobias * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef WII_REMOTE_H +#define WII_REMOTE_H + +/* Toggle one bit */ +#define ToggleBit(bf,b) (bf) = ((bf) & b) ? ((bf) & ~(b)) : ((bf) | (b)) + +//Settings +#define WIIREMOTE_SAMPLES 16 + +#define DEADZONE_Y 0.3f +#define DEADZONE_X 0.5f + +#define MOUSE_MAX 65535 +#define MOUSE_MIN 0 + +//The work area is from 0 - MAX but the one sent to XBMC is MIN - (MIN + MOUSE_MAX) +#define WIIREMOTE_X_MIN MOUSE_MAX * DEADZONE_X +#define WIIREMOTE_Y_MIN MOUSE_MAX * DEADZONE_Y + +#define WIIREMOTE_X_MAX MOUSE_MAX * (1.0f + DEADZONE_X + DEADZONE_X) +#define WIIREMOTE_Y_MAX MOUSE_MAX * (1.0f + DEADZONE_Y + DEADZONE_Y) + +#define WIIREMOTE_BUTTON_REPEAT_TIME 30 // How long between buttonpresses in repeat mode +#define WIIREMOTE_BUTTON_DELAY_TIME 500 +//#define CWIID_OLD // Uncomment if the system is running cwiid that is older than 6.0 (The one from ubuntu gutsy repository is < 6.0) + +//CWIID +#include <cwiid.h> +//Bluetooth specific +#include <sys/socket.h> +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +// UDP Client +#ifdef DEB_PACK +#include <xbmc/xbmcclient.h> +#else +#include "../../lib/c++/xbmcclient.h" +#endif +/*#include <stdio.h>*/ +#include <stdlib.h> +#include <time.h> +#include <sys/time.h> +#include <string> + +class CWiiRemote +{ +public: + CWiiRemote(char *btaddr = NULL); + ~CWiiRemote(); + + void Initialize(CAddress Addr, int Socket); + void Disconnect(); + bool GetConnected(); + + bool EnableWiiRemote(); + bool DisableWiiRemote(); + + void Update(); + + // Mouse functions + bool HaveIRSources(); + bool isActive(); + void EnableMouseEmulation(); + void DisableMouseEmulation(); + + bool Connect(); + + void SetBluetoothAddress(const char * btaddr); + void SetSensativity(float DeadX, float DeadY, int Samples); + void SetJoystickMap(const char *JoyMap); +private: + int m_NumSamples; + int *m_SamplesX; + int *m_SamplesY; + + float m_MaxX; + float m_MaxY; + float m_MinX; + float m_MinY; +#ifdef CWIID_OLD + bool CheckConnection(); + int m_LastMsgTime; +#endif + char *m_JoyMap; + int m_lastKeyPressed; + int m_LastKey; + bool m_buttonRepeat; + + int m_lastKeyPressedNunchuck; + int m_LastKeyNunchuck; + bool m_buttonRepeatNunchuck; + + void SetRptMode(); + void SetLedState(); + + void SetupWiiRemote(); + + bool m_connected; + + bool m_DisconnectWhenPossible; + bool m_connectThreadRunning; + + //CWIID Specific + cwiid_wiimote_t *m_wiiremoteHandle; + unsigned char m_ledState; + unsigned char m_rptMode; + bdaddr_t m_btaddr; + + static void MessageCallback(cwiid_wiimote_t *wiiremote, int mesgCount, union cwiid_mesg mesg[], struct timespec *timestamp); +#ifdef CWIID_OLD + static void MessageCallback(cwiid_wiimote_t *wiiremote, int mesgCount, union cwiid_mesg mesg[]); +#endif + +#ifndef _DEBUG +/* This takes the errors generated at pre-connect and silence them as they are mostly not needed */ + static void ErrorCallback(struct wiimote *wiiremote, const char *str, va_list ap); +#endif + + int m_Socket; + CAddress m_MyAddr; + + // Mouse + bool m_haveIRSources; + bool m_isActive; + bool m_useIRMouse; + int m_lastActiveTime; + +/* The protected functions is for the static callbacks */ + protected: + //Connection + void DisconnectNow(bool startConnectThread); + + //Mouse + void CalculateMousePointer(int x1, int y1, int x2, int y2); +// void SetIR(bool set); + + //Button + void ProcessKey(int Key); + + //Nunchuck + void ProcessNunchuck(struct cwiid_nunchuk_mesg &Nunchuck); +#ifdef CWIID_OLD + //Disconnect check + void CheckIn(); +#endif +}; + +#endif // WII_REMOTE_H + +extern CWiiRemote g_WiiRemote; diff --git a/tools/EventClients/Clients/WiiRemote/Makefile b/tools/EventClients/Clients/WiiRemote/Makefile new file mode 100644 index 0000000000..4ceaa68bbb --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/Makefile @@ -0,0 +1,13 @@ +CFLAGS = -Wall -pipe -fPIC -funroll-loops +OBJ_DIR = release-$(shell $(CC) -v 2>&1|grep ^Target:|cut -d' ' -f2) +OBJS = wiiuse_v0.12/src/$(OBJ_DIR)/libwiiuse.so +BIN = WiiUse_WiiRemote +VERSION = v0.12 + +all: + @$(MAKE) -C wiiuse_$(VERSION)/src $@ + g++ $(CFLAGS) WiiUse_WiiRemote.cpp $(OBJS) -o $(BIN) +wiiuse: + @$(MAKE) -C wiiuse_$(VERSION)/src +clean: + rm $(OBJS) $(BIN) diff --git a/tools/EventClients/Clients/WiiRemote/WiiUse_README b/tools/EventClients/Clients/WiiRemote/WiiUse_README new file mode 100644 index 0000000000..df67c2d7cf --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/WiiUse_README @@ -0,0 +1,18 @@ +There are two ways to make the WiiUse_WiiRemote event client. + +1) Compile manually. Requires the wiiuse library to be installed already (http://sourceforge.net/project/showfiles.php?group_id=187194) +g++ -lwiiuse WiiUse_WiiRemote.cpp -o WiiUse_WiiRemote + +2) Use the Makefile. Will make the wiiuse_v0.12 library from source. Executable must be run from this directory due to relative linking to the built library. +make + +Both methods will create a WiiUse_WiiRemote executable containing the event client. + + +The WiiUse_WiiRemote.h file contains the button number mappings for the Wii Remote (prefixed with KEYCODE). + + +TODO: +Add IR pointer support + +TheUni diff --git a/tools/EventClients/Clients/WiiRemote/WiiUse_WiiRemote.cpp b/tools/EventClients/Clients/WiiRemote/WiiUse_WiiRemote.cpp new file mode 100644 index 0000000000..bae0cf6f49 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/WiiUse_WiiRemote.cpp @@ -0,0 +1,264 @@ + /* Copyright (C) 2009 by Cory Fields + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * + */ + +#include "WiiUse_WiiRemote.h" + +void CWiiController::get_keys(wiimote* wm) +{ + m_buttonHeld = 0; + m_buttonPressed = 0; + m_buttonReleased = 0; + + m_repeatableHeld = (wm->btns & (m_repeatFlags)); + m_holdableHeld = (wm->btns & (m_holdFlags)); + m_repeatableReleased = (wm->btns_released & (m_repeatFlags)); + m_holdableReleased = (wm->btns_released & (m_holdFlags)); + + for (int i = 1;i <= WIIMOTE_NUM_BUTTONS; i++) + { + if (IS_PRESSED(wm,convert_code(i))) + m_buttonPressed = i; + if (IS_RELEASED(wm,convert_code(i))) + m_buttonReleased = i; + if (IS_HELD(wm,convert_code(i))) + m_buttonHeld = i; + } +} + +void CWiiController::handleKeyPress() +{ + if ((m_holdableReleased && m_buttonDownTime < g_hold_button_timeout)) + EventClient.SendButton(m_buttonReleased, m_joyString, BTN_QUEUE | BTN_NO_REPEAT); + if (m_buttonPressed && !m_holdableHeld && !m_buttonHeld) + EventClient.SendButton(m_buttonPressed, m_joyString, BTN_QUEUE | BTN_NO_REPEAT); +} + +void CWiiController::handleRepeat() +{ + if (m_repeatableHeld) + EventClient.SendButton(m_buttonPressed, m_joyString, BTN_QUEUE | BTN_NO_REPEAT, 5); +} + +void CWiiController::handleACC(float currentRoll, float currentPitch) +{ + int rollWeight = 0; + int tiltWeight = 0; + + if (m_start_roll == 0) + m_start_roll = currentRoll; + m_abs_roll = smoothDeg(m_abs_roll, currentRoll); + m_rel_roll = m_abs_roll - m_start_roll; + rollWeight = int((m_rel_roll*m_rel_roll)); + if (rollWeight > 65000) + rollWeight = 65000; + + if (m_start_pitch == 0) + m_start_pitch = currentPitch; + m_abs_pitch = smoothDeg(m_abs_pitch, currentPitch); + m_rel_pitch = m_start_pitch - m_abs_pitch; + tiltWeight = int((m_rel_pitch*m_rel_pitch*16)); + if (tiltWeight > 65000) + tiltWeight = 65000; + + if (m_currentAction == ACTION_NONE) + { + if ((g_deadzone - (abs((int)m_rel_roll)) < 5) && (abs((int)m_abs_pitch) < (g_deadzone / 1.5))) + // crossed the roll deadzone threshhold while inside the pitch deadzone + { + m_currentAction = ACTION_ROLL; + } + else if ((g_deadzone - (abs((int)m_rel_pitch)) < 5) && (abs((int)m_abs_roll) < (g_deadzone / 1.5))) + // crossed the pitch deadzone threshhold while inside the roll deadzone + { + m_currentAction = ACTION_PITCH; + } + } + + if (m_currentAction == ACTION_ROLL) + { + if (m_rel_roll < -g_deadzone) + EventClient.SendButton(KEYCODE_ROLL_NEG, m_joyString, BTN_QUEUE | BTN_NO_REPEAT, rollWeight); + if (m_rel_roll > g_deadzone) + EventClient.SendButton(KEYCODE_ROLL_POS, m_joyString, BTN_QUEUE | BTN_NO_REPEAT, rollWeight); +// printf("Roll: %f\n",m_rel_roll); + } + + if (m_currentAction == ACTION_PITCH) + { + if (m_rel_pitch > g_deadzone) + EventClient.SendButton(KEYCODE_PITCH_POS, m_joyString, BTN_QUEUE | BTN_NO_REPEAT,tiltWeight); + if (m_rel_pitch < -g_deadzone) + EventClient.SendButton(KEYCODE_PITCH_NEG, m_joyString, BTN_QUEUE | BTN_NO_REPEAT,tiltWeight); +// printf("Pitch: %f\n",m_rel_pitch); + } +} + +void CWiiController::handleIR() +{ +//TODO +} + +int connectWiimote(wiimote** wiimotes) +{ + int found = 0; + int connected = 0; + wiimote* wm; + while(!found) + { + //Look for Wiimotes. Keep searching until one is found. Wiiuse provides no way to search forever, so search for 5 secs and repeat. + found = wiiuse_find(wiimotes, MAX_WIIMOTES, 5); + } + connected = wiiuse_connect(wiimotes, MAX_WIIMOTES); + wm = wiimotes[0]; + if (connected) + { + EventClient.SendHELO("Wii Remote", ICON_PNG, NULL); + wiiuse_set_leds(wm, WIIMOTE_LED_1); + wiiuse_rumble(wm, 1); + wiiuse_set_orient_threshold(wm,1); + #ifndef WIN32 + usleep(200000); + #else + Sleep(200); + #endif + wiiuse_rumble(wm, 0); + + return 1; + } + return 0; +} + +int handle_disconnect(wiimote* wm) +{ + EventClient.SendNOTIFICATION("Wii Remote disconnected", "", ICON_PNG, NULL); + return 0; +} + +unsigned short convert_code(unsigned short client_code) +{ +//WIIMOTE variables are from wiiuse. We need them to be 1-11 for the eventclient. +//For that we use this ugly conversion. + + switch (client_code) + { + case KEYCODE_BUTTON_UP: return WIIMOTE_BUTTON_UP; + case KEYCODE_BUTTON_DOWN: return WIIMOTE_BUTTON_DOWN; + case KEYCODE_BUTTON_LEFT: return WIIMOTE_BUTTON_LEFT; + case KEYCODE_BUTTON_RIGHT: return WIIMOTE_BUTTON_RIGHT; + case KEYCODE_BUTTON_A: return WIIMOTE_BUTTON_A; + case KEYCODE_BUTTON_B: return WIIMOTE_BUTTON_B; + case KEYCODE_BUTTON_MINUS: return WIIMOTE_BUTTON_MINUS; + case KEYCODE_BUTTON_HOME: return WIIMOTE_BUTTON_HOME; + case KEYCODE_BUTTON_PLUS: return WIIMOTE_BUTTON_PLUS; + case KEYCODE_BUTTON_ONE: return WIIMOTE_BUTTON_ONE; + case KEYCODE_BUTTON_TWO: return WIIMOTE_BUTTON_TWO; + default : break; + } +return 0; +} + +float smoothDeg(float oldVal, float newVal) +{ +//If values go over 180 or under -180, weird things happen. This tranforms the values to -360 to 360 instead. + + if (newVal - oldVal > 300) + return (newVal - 360); + if (oldVal - newVal > 300) + return(newVal + 360); + return(newVal); +} + +long getTicks(void) +{ + long ticks; + struct timeval now; + gettimeofday(&now, NULL); + ticks = now.tv_sec * 1000l; + ticks += now.tv_usec / 1000l; + return ticks; +} + +int main(int argc, char** argv) +{ + long timeout = 0; + bool connected = 0; + wiimote** wiimotes; + wiimotes = wiiuse_init(MAX_WIIMOTES); + CWiiController controller; + wiimote* wm; + { + //Main Loop + while (1) + { + if (!connected) connected = (connectWiimote(wiimotes)); + wm = wiimotes[0]; // Only worry about 1 controller. No need for more? + controller.m_buttonDownTime = getTicks() - timeout; + +//Handle ACC, Repeat, and IR outside of the Event loop so that buttons can be continuously sent + if (timeout) + { + if ((controller.m_buttonDownTime > g_hold_button_timeout) && controller.m_holdableHeld) + controller.handleACC(wm->orient.roll, wm->orient.pitch); + if ((controller.m_buttonDownTime > g_repeat_rate) && controller.m_repeatableHeld) + controller.handleRepeat(); + } + if (wiiuse_poll(wiimotes, MAX_WIIMOTES)) + { + for (int i = 0; i < MAX_WIIMOTES; ++i) + //MAX_WIIMOTES hardcoded at 1. + { + switch (wiimotes[i]->event) + { + case WIIUSE_EVENT: + + controller.get_keys(wm); //Load up the CWiiController + controller.handleKeyPress(); + if (!controller.m_buttonHeld && (controller.m_holdableHeld || controller.m_repeatableHeld)) + { + //Prepare to repeat or hold. Do this only once. + timeout = getTicks(); + EnableMotionSensing(wm); + controller.m_abs_roll = 0; + controller.m_abs_pitch = 0; + controller.m_start_roll = 0; + controller.m_start_pitch = 0; + } + if (controller.m_buttonReleased) + { + DisableMotionSensing(wm); + controller.m_currentAction = ACTION_NONE; + } + break; + case WIIUSE_STATUS: + break; + case WIIUSE_DISCONNECT: + case WIIUSE_UNEXPECTED_DISCONNECT: + handle_disconnect(wm); + connected = 0; + break; + case WIIUSE_READ_DATA: + break; + default: + break; + } + } + } + } + } + wiiuse_cleanup(wiimotes, MAX_WIIMOTES); + return 0; +} diff --git a/tools/EventClients/Clients/WiiRemote/WiiUse_WiiRemote.h b/tools/EventClients/Clients/WiiRemote/WiiUse_WiiRemote.h new file mode 100644 index 0000000000..b4eb749b9e --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/WiiUse_WiiRemote.h @@ -0,0 +1,124 @@ + /* Copyright (C) 2009 by Cory Fields + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * + */ + + +#include <stdio.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/time.h> +//#include <math.h> +#ifdef DEB_PACK +#include <xbmc/xbmcclient.h> +#else +#include "../../lib/c++/xbmcclient.h" +#endif +//#ifndef WIN32 +// #include <unistd.h> +//#endif +#include "wiiuse.h" +//#define ICON_PATH "../../" +#define PORT 9777 +#define MAX_WIIMOTES 1 +#define OVER_180_DEG 1 +#define UNDER_NEG_180_DEG 2 +#define WIIMOTE_BUTTON_TWO 0x0001 +#define WIIMOTE_BUTTON_ONE 0x0002 +#define WIIMOTE_BUTTON_B 0x0004 +#define WIIMOTE_BUTTON_A 0x0008 +#define WIIMOTE_BUTTON_MINUS 0x0010 +#define WIIMOTE_BUTTON_HOME 0x0080 +#define WIIMOTE_BUTTON_LEFT 0x0100 +#define WIIMOTE_BUTTON_RIGHT 0x0200 +#define WIIMOTE_BUTTON_DOWN 0x0400 +#define WIIMOTE_BUTTON_UP 0x0800 +#define WIIMOTE_BUTTON_PLUS 0x1000 + +#define WIIMOTE_NUM_BUTTONS 11 +#define KEYCODE_BUTTON_UP 1 +#define KEYCODE_BUTTON_DOWN 2 +#define KEYCODE_BUTTON_LEFT 3 +#define KEYCODE_BUTTON_RIGHT 4 +#define KEYCODE_BUTTON_A 5 +#define KEYCODE_BUTTON_B 6 +#define KEYCODE_BUTTON_MINUS 7 +#define KEYCODE_BUTTON_HOME 8 +#define KEYCODE_BUTTON_PLUS 9 +#define KEYCODE_BUTTON_ONE 10 +#define KEYCODE_BUTTON_TWO 11 +#define KEYCODE_ROLL_NEG 33 +#define KEYCODE_ROLL_POS 34 +#define KEYCODE_PITCH_NEG 35 +#define KEYCODE_PITCH_POS 36 + + +#define ACTION_NONE 0 +#define ACTION_ROLL 1 +#define ACTION_PITCH 2 + +class CWiiController{ + public: + + bool m_holdableHeld; + bool m_holdableReleased; + bool m_repeatableHeld; + bool m_repeatableReleased; + + unsigned short m_buttonPressed; + unsigned short m_buttonReleased; + unsigned short m_buttonHeld; + unsigned short m_repeatFlags; + unsigned short m_holdFlags; + unsigned short m_currentAction; + + long m_buttonDownTime; + + float m_abs_roll; + float m_abs_pitch; + float m_rel_roll; + float m_rel_pitch; + float m_start_roll; + float m_start_pitch; + char* m_joyString; + + + void get_keys(wiimote* wm); + void handleKeyPress(); + void handleRepeat(); + void handleACC(float, float); + void handleIR(); + + CWiiController() + { + m_joyString = "JS0:WiiRemote"; + m_repeatFlags = WIIMOTE_BUTTON_UP | WIIMOTE_BUTTON_DOWN | WIIMOTE_BUTTON_LEFT | WIIMOTE_BUTTON_RIGHT; + m_holdFlags = WIIMOTE_BUTTON_B; + } +}; + +unsigned short g_deadzone = 30; +unsigned short g_hold_button_timeout = 200; +unsigned short g_repeat_rate = 400; + +int handle_disconnect(wiimote* ); +int connectWiimote(wiimote** ); +long getTicks(void); +void EnableMotionSensing(wiimote* wm) {if (!WIIUSE_USING_ACC(wm)) wiiuse_motion_sensing(wm, 1);} +void DisableMotionSensing(wiimote* wm) {if (WIIUSE_USING_ACC(wm)) wiiuse_motion_sensing(wm, 0);} +unsigned short convert_code(unsigned short); +float smoothDeg(float, float); + +CXBMCClient EventClient; diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/CHANGELOG b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/CHANGELOG new file mode 100644 index 0000000000..ccb831af97 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/CHANGELOG @@ -0,0 +1,208 @@ +---------------------------
+-
+- CHANGE LOG - Wiiuse
+-
+---------------------------
+
+http://wiiuse.net/
+http://wiiuse.sourceforge.net/
+http://sourceforge.net/projects/wiiuse/
+
+---------------------------
+v0.12 -- 2 Apr 2008
+---------------------------
+
+ Added:
+ - API function wiiuse_set_ir_sensitivity()
+ - Macro WIIUSE_GET_IR_SENSITIVITY()
+ - Event type WIIUSE_READ_DATA
+ - Event type WIIUSE_UNEXPECTED_DISCONNECT
+
+ Fixed:
+ - [Linux] Ability to try to select() nothing
+ - [Linux] Changed Makefile to include debug output when compiling in debug mode
+
+ Changed:
+ - wiiuse_set_nunchuk_orient_threshold() now takes a wiimote_t pointer
+ - wiiuse_set_nunchuk_accel_threshold() now takes a wiimote_t pointer
+ - wiiuse_read_data() generates an event WIIUSE_READ_DATA rather than executing a callback
+
+---------------------------
+v0.11 -- 25 Feb 2008
+---------------------------
+
+ Added:
+ - API function wiiuse_set_nunchuk_orient_threshold()
+ - API function wiiuse_set_nunchuk_accel_threshold()
+ - Event type WIIUSE_NUNCHUK_INSERTED
+ - Event type WIIUSE_NUNCHUK_REMOVED
+ - Event type WIIUSE_CLASSIC_CTRL_INSERTED
+ - Event type WIIUSE_CLASSIC_CTRL_REMOVED
+ - Event type WIIUSE_GUITAR_HERO_3_CTRL_INSERTED
+ - Event type WIIUSE_GUITAR_HERO_3_CTRL_REMOVED
+
+ Fixed:
+ - Added some missing function prototypes to wiiuse.h
+ - [Linux] Fixed Makefile to link libmath and libbluetooth
+ - Status event is set when a status report comes in
+ - Orientation threshold not being saved in lstate
+
+---------------------------
+v0.10 -- 11 Feb 2008
+---------------------------
+
+ Added:
+ - Real dynamic linking (by noisehole)
+ - Changed from callback to SDL style
+ - Guitar Hero 3 controller support
+ - API function wiiuse_set_accel_threshold()
+ - API function wiiuse_version()
+ - Macro WIIUSE_USING_SPEAKER()
+ - Macro WIIUSE_IS_LED_SET(wm, num)
+ - wiiuse_init() now autogenerates unids
+ - orient_t::a_roll/a_pitch
+ - wiiuse_resync()
+ - wiiuse_cleanup()
+ - wiiuse_set_timeout()
+
+ Fixed:
+ - [Windows] Fixed bug where it did not detect expansions on startup
+ - Renamed INFO/WARNING/DEBUG macros to WIIUSE_* (by noisehole)
+ - Updated Makefiles (by noisehole)
+ - Fixed incorrect roll/pitch when smoothing was enabled
+ - Fixed nunchuk and classic controller flooding events when significant changes occured
+ - Fixed bug where IR was not correct on roll if IR was enabled before handshake
+
+ Removed:
+ - wiiuse_startup(), no longer needed
+
+---------------------------
+v0.9 -- 3 Nov 2007
+---------------------------
+
+ Fixed:
+ - Can now use include/wiiuse.h in C++ projects.
+ - HOME button works again.
+ - IR now functions after expansion is connected or removed.
+
+---------------------------
+v0.8 -- 27 Oct 2007
+---------------------------
+
+ - Bumped API version to 8
+ - Exported all API functions for usage with non-C/C++ languages.
+ - Changed event callback to only trigger if a significant state change occurs.
+
+ Added:
+ - wiimote_t::lstate structure
+
+ Fixed:
+ - Bug 1820140 - Buffer overflow in io_nix.c. Thanks proppy.
+
+---------------------------
+v0.7 -- 19 Oct 2007
+---------------------------
+
+ - Bumped API version to 7
+ - Renamed Linux build from wii.so to wiiuse.so
+ - Changed version representation from float to const char*.
+
+ Added:
+ - [Windows] BlueSoleil support.
+ - [Windows] Bluetooth stack auto-detection (WinXP SP2, Bluesoleil, Widdcomm tested).
+ - [Windows] API function wiiuse_set_bluetooth_stack().
+ - Calculates yaw if IR tracking is enabled.
+
+ Fixed:
+ - [Windows] Problem where a connection is made to a wiimote that does not exist.
+ - [Windows] Issue that occured while using multiple wiimotes.
+
+---------------------------
+v0.6 -- 16 Oct 2007
+---------------------------
+
+ - Bumped API version to 0.6.
+ - Ported to Microsoft Windows.
+ - Improved IR tracking.
+ - Default IR virtual screen resolutions changed depending on 16:9 or 4:3.
+
+ Added:
+ - src/msvc/ and api/msvc/ - Microsoft Visual C++ 6.0 project files.
+
+---------------------------
+v0.5 -- 13 Oct 2007
+---------------------------
+
+ - Bumped API version to 0.5.
+ - Greatly improved IR tracking.
+ - Renamed function wiiuse_set_ir_correction() to wiiuse_set_ir_position().
+
+ Added:
+ - API function wiiuse_set_aspect_ratio()
+
+ Fixed:
+ - When rolling around 180 degree rotation smoothing would not be seemless.
+
+---------------------------
+v0.4 -- 08 Oct 2007
+---------------------------
+
+ - Bumped API version to 0.4.
+ - Greatly improved classic controller joystick functionality.
+ - Changed all functions named wiimote_*() to wiiuse_*()
+ - Renamed many macros from WIIMOTE_* to WIIUSE_*
+
+ Added:
+ - IR support
+ - New WIIMOTE_CONTINUOUS flag to set continuous reporting
+ - Macro IS_JUST_PRESSED()
+ - Macro WIIUSE_USING_ACC()
+ - Macro WIIUSE_USING_EXP()
+ - Macro WIIUSE_USING_IR()
+ - API function wiiuse_set_ir()
+ - API function wiiuse_set_ir_vres()
+ - API function wiiuse_set_ir_correction()
+
+ - gfx/ - A small OpenGL example that renders IR data
+
+ Fixed:
+ - Sometimes classic controller would only report infinite angle and magnitude for joysticks.
+
+---------------------------
+v0.3 -- 10 Sept 2007
+---------------------------
+
+ - Moved license to GPLv3.
+ - Bumped API version to 0.3.
+
+ Added:
+ - Support for Classic Controller
+ - Smoothing for roll and pitch values of the wiimote and nunchuk.
+
+ - API: wiimote_set_flags() to set or disable wiimote options.
+ - API: wiimote_set_smooth_alpha() to set smoothing alpha value.
+
+ Fixed:
+ - When the wiimote accelerates the roll or pitch is unreliable and was set to 0.
+ It now remains at previous tilt value.
+ - If no event callback was specified then no events would be processed internally.
+
+---------------------------
+v0.2 -- 25 Aug 2007
+---------------------------
+
+ - Bumped API version to 0.2.
+
+ Added:
+ - Nunchuk support.
+ - Ability to write to flash memory.
+
+ Fixed:
+ - Roll and pitch rotation now ranges from -180 to 180 degrees (previously -90 to 90).
+ - Bug when reading data from flash memory would read wrong address.
+
+---------------------------
+v0.1 -- 23 Feb 2007
+---------------------------
+
+ - Initial release
diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/LICENSE b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/LICENSE new file mode 100644 index 0000000000..85b8e59922 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/LICENSE @@ -0,0 +1,620 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/LICENSE_noncommercial b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/LICENSE_noncommercial new file mode 100644 index 0000000000..b556a2278c --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/LICENSE_noncommercial @@ -0,0 +1,170 @@ +This license can be used for any works that this license defines +as "derived" or "combined" works that are strictly NON-COMMERCIAL. +This license prohibits the use of any source or object code +in works that are PROPRIETARY. + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/Makefile b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/Makefile new file mode 100644 index 0000000000..f63da694f8 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/Makefile @@ -0,0 +1,23 @@ +# +# wiiuse Makefile +# + +all clean install: + @$(MAKE) -C src $@ + @$(MAKE) -C example $@ + @$(MAKE) -C example-sdl $@ + +wiiuse: + @$(MAKE) -C src + +ex: + @$(MAKE) -C example + +sdl-ex: + @$(MAKE) -C example-sdl + +distclean: + @$(MAKE) -C src $@ + @$(MAKE) -C example $@ + @$(MAKE) -C example-sdl $@ + @find . -type f \( -name "*.save" -or -name "*~" -or -name gmon.out \) -delete &> /dev/null diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/README b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/README new file mode 100644 index 0000000000..12d9f71517 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/README @@ -0,0 +1,155 @@ +---------------------------
+-
+- README - Wiiuse
+-
+---------------------------
+
+http://wiiuse.net/
+http://wiiuse.sourceforge.net/
+http://sourceforge.net/projects/wiiuse/
+
+---------------------------
+
+ABOUT
+
+ Wiiuse is a library written in C that connects with several Nintendo Wii remotes.
+ Supports motion sensing, IR tracking, nunchuk, classic controller, and the Guitar Hero 3 controller.
+ Single threaded and nonblocking makes a light weight and clean API.
+
+ Distributed under the GPL and LGPL.
+
+
+AUTHORS
+
+ Michael Laforest < para >
+ Email: < thepara (--AT--) g m a i l [--DOT--] com >
+
+ The following people have contributed patches to wiiuse:
+ - dhewg
+
+
+LICENSE
+
+ There are two licenses for wiiuse. Please read them carefully before choosing which one
+ you use. You may of course at any time switch the license you are currently using to
+ the other.
+
+ Briefly, the license options are:
+
+ a) GNU LGPL (modified for non-commercial usage only)
+ b) GNU GPL
+
+ PLEASE READ THE LICENSES!
+
+
+ACKNOWLEDGEMENTS
+
+ http://wiibrew.org/
+ This site and their users have contributed an immense amount of information
+ about the wiimote and its technical details. I could not have written this program
+ without the vast amounts of reverse engineered information that was researched by them.
+
+ Nintendo
+ Of course Nintendo for designing and manufacturing the Wii and Wii remote.
+
+ BlueZ
+ Easy and intuitive bluetooth stack for Linux.
+
+ Thanks to Brent for letting me borrow his Guitar Hero 3 controller.
+
+
+DISCLAIMER AND WARNINGS
+
+ I am in no way responsible for any damages or effects, intended or not, caused by this program.
+
+ *** WARNING *** WARNING *** WARNING ***
+
+ Be aware that writing to memory may damage or destroy your wiimote or expansions.
+
+ *** WARNING *** WARNING *** WARNING ***
+
+ This program was written using reverse engineered specifications available from wiibrew.org.
+ Therefore the operation of this program may not be entirely correct.
+ Results obtained by using this program may vary.
+
+
+AUDIENCE
+
+ This project is intended for developers who wish to include support for the Nintendo Wii remote
+ with their third party application. Please be aware that by using this software you are bound
+ to the terms of the GNU GPL.
+
+
+PLATFORMS AND DEPENDENCIES
+
+ Wiiuse currently operates on both Linux and Windows.
+ You will need:
+
+ For Linux:
+ - The kernel must support bluetooth
+ - The BlueZ bluetooth drivers must be installed
+
+ For Windows:
+ - Bluetooth driver (tested with Microsoft's stack with Windows XP SP2)
+ - If compiling, Microsoft Windows Driver Development Kit (DDK)
+
+
+COMPILING
+
+ Linux:
+ You need SDL and OpenGL installed to compile the SDL example.
+
+ # make [target]
+
+ If 'target' is omitted then everything is compiled.
+
+ Where 'target' can be any of the following:
+
+ - wiiuse
+ Compiles libwiiuse.so
+
+ - ex
+ Compiles wiiuse-example
+
+ - sdl-ex
+ Compiles wiiuse-sdl
+
+ Become root.
+
+ # make install
+
+ The above command will only install the binaries that you
+ selected to compile.
+
+ wiiuse.so is installed to /usr/lib
+ wiiuse-example and wiiuse-sdl are installed to /usr/bin
+
+ Windows:
+ A Microsoft Visual C++ 6.0 project file has been included.
+
+ You need the install the Windows DDK (driver development kit)
+ to compile wiiuse. Make sure you include the inc/ and lib/
+ directories from the DDK in your IDE include and library paths.
+ You can download this from here:
+ http://www.microsoft.com/whdc/devtools/ddk/default.mspx
+
+ You need to link the following libraries (already set in the
+ included project file):
+ - Ws2_32.lib
+ - hid.lib
+ - setupapi.lib
+
+
+USING THE LIBRARY IN A THIRD PARTY APPLICATION
+
+ To use the library in your own program you must first compile wiiuse as a module.
+ Include include/wiiuse.h in any file that uses wiiuse.
+
+ For Linux you must link wiiuse.so ( -lwiiuse ).
+ For Windows you must link wiiuse.lib. When your program runs it will need wiiuse.dll.
+
+
+BUGS
+
+ On Windows using more than one wiimote (usually more than two wiimotes) may cause
+ significant latency.
diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/Makefile b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/Makefile new file mode 100644 index 0000000000..c206354141 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/Makefile @@ -0,0 +1,83 @@ +# +# wiiuse Makefile +# + +# +# Change this to your GCC version. +# +CC = gcc + +#################################################### +# +# You should not need to edit below this line. +# +#################################################### + +# +# Universal cflags +# +CFLAGS = -Wall -pipe -fPIC -funroll-loops + +ifeq ($(debug),1) + OBJ_PREFIX = debug + CFLAGS += -g -pg -DWITH_WIIUSE_DEBUG +else + OBJ_PREFIX = release + CFLAGS += -O2 +endif + +OBJ_DIR = $(OBJ_PREFIX)-$(shell $(CC) -v 2>&1|grep ^Target:|cut -d' ' -f2) + +# +# Linking flags +# +LDFLAGS = -L../src/$(OBJ_DIR) -lm -lGL -lGLU -lglut -lSDL -lbluetooth -lwiiuse + +# +# Target binaries (always created as BIN) +# +BIN = ./$(OBJ_DIR)/wiiuse-sdl + +# +# Inclusion paths. +# +INCLUDES = -I../src/ -I/usr/include/SDL + +# +# Generate a list of object files +# +OBJS = $(OBJ_DIR)/sdl.o + +############################### +# +# Build targets. +# +############################### + +all: $(BIN) + +clean: + @-rm $(OBJS) 2> /dev/null + +distclean: clean + @-rm -r debug-* release-* 2> /dev/null + +install: + @if [ -e $(BIN) ]; then \ + cp -v $(BIN) /usr/bin ;\ + fi + +$(BIN): mkdir $(OBJS) + $(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) -o $(BIN) + +$(OBJ_DIR)/%.o: %.c + $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ + +mkdir: + @if [ ! -d $(OBJ_DIR) ]; then \ + mkdir $(OBJ_DIR); \ + fi + +run: all + LD_LIBRARY_PATH=`pwd`/../src/$(OBJ_DIR):$(LD_LIBRARY_PATH) $(BIN) + diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/msvc/wiiusesdl.dsp b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/msvc/wiiusesdl.dsp new file mode 100644 index 0000000000..23c4a24c2a --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/msvc/wiiusesdl.dsp @@ -0,0 +1,111 @@ +# Microsoft Developer Studio Project File - Name="wiiusesdl" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Application" 0x0101
+
+CFG=wiiusesdl - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "wiiusesdl.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "wiiusesdl.mak" CFG="wiiusesdl - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "wiiusesdl - Win32 Release" (based on "Win32 (x86) Application")
+!MESSAGE "wiiusesdl - Win32 Debug" (based on "Win32 (x86) Application")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "wiiusesdl - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /W3 /GX /O2 /I "..\..\src" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib sdl.lib opengl32.lib glu32.lib wiiuse.lib /nologo /subsystem:windows /machine:I386
+
+!ELSEIF "$(CFG)" == "wiiusesdl - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c
+# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\src" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib sdl.lib opengl32.lib glu32.lib wiiuse.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "wiiusesdl - Win32 Release"
+# Name "wiiusesdl - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=..\sdl.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE=..\..\src\wiiuse.h
+# End Source File
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/msvc/wiiusesdl.dsw b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/msvc/wiiusesdl.dsw new file mode 100644 index 0000000000..630b942593 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/msvc/wiiusesdl.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00
+# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
+
+###############################################################################
+
+Project: "wiiusesdl"=".\wiiusesdl.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Global:
+
+Package=<5>
+{{{
+}}}
+
+Package=<3>
+{{{
+}}}
+
+###############################################################################
+
diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/msvc/wiiusesdl.opt b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/msvc/wiiusesdl.opt Binary files differnew file mode 100644 index 0000000000..fa46687ca8 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/msvc/wiiusesdl.opt diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/sdl.c b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/sdl.c new file mode 100644 index 0000000000..0c0cdfb993 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/sdl.c @@ -0,0 +1,439 @@ +/* + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + */ + +#include <stdlib.h> +#include <math.h> + +#ifndef WIN32 + #include <unistd.h> + #include <sys/time.h> + #include <time.h> +#else + #include <windows.h> +#endif + +#include <GL/gl.h> +#include <GL/glu.h> +#include <GL/glut.h> +#include <SDL.h> + +#include <wiiuse.h> + +#define PI 3.14159265358979323846 +#define PI_DIV_180 0.017453292519943296 +#define deg PI_DIV_180 + +#define MAX_WIIMOTES 2 + +GLint width = 1024, height = 768; +GLfloat backColor[4] = {1.0,1.0,1.0,1.0}; + +wiimote** wiimotes = NULL; + +int last_dots[4][2] = {{0}}; +int xcoord = 0; +int ycoord = 0;
+
+#ifdef WIN32
+ DWORD last_render; +#else + struct timeval last_render; + int last_sec = 0;
+ int fps = 0;
+#endif + +enum render_mode_t { + IR = 1, + TEAPOT +}; +enum render_mode_t render_mode = IR; + +/* light information */ +struct light_t { + GLfloat position[4]; + GLfloat spotDirection[3]; + GLfloat ambient[4]; + GLfloat diffuse[4]; + GLfloat specular[4]; + GLfloat spotCutoff; + GLfloat spotExponent; + GLfloat spotAttenuation[3]; /* [0] = constant, [1] = linear, [2] = quadratic */ +}; +struct light_t light = { + { 1.0, 1.0, -2.0, 1.0 }, + { -1.0, -1.0, 2.0 }, + { 0.0, 0.0, 0.0, 1.0 }, + { 1.0, 1.0, 1.0, 1.0 }, + { 1.0, 1.0, 1.0, 1.0 }, + 180.0, 0.0, + { 1.0, 0.0, 0.0 } +}; + +/* material information */ +struct material_t { + GLfloat ambient[4]; + GLfloat diffuse[4]; + GLfloat specular[4]; + GLfloat emission[4]; + GLfloat shininess; +}; +struct material_t red_plastic = { + { 0.3, 0.0, 0.0, 1.0 }, + { 0.3, 0.0, 0.0, 1.0 }, + { 0.8, 0.6, 0.6, 1.0 }, + { 0.0, 0.0, 0.0, 1.0 }, + 32.0 +}; + + +void handle_event(struct wiimote_t* wm); +void display(); +void update_light(GLenum l, struct light_t* lptr); +void set_material(struct material_t* mptr); +void resize_window(GLint new_width, GLint new_height); + +void handle_event(struct wiimote_t* wm) { + if (IS_JUST_PRESSED(wm, WIIMOTE_BUTTON_PLUS)) + wiiuse_motion_sensing(wm, 1); + if (IS_JUST_PRESSED(wm, WIIMOTE_BUTTON_MINUS)) + wiiuse_motion_sensing(wm, 0); + + if (IS_JUST_PRESSED(wm, WIIMOTE_BUTTON_UP)) + wiiuse_set_ir(wm, 1); + if (IS_JUST_PRESSED(wm, WIIMOTE_BUTTON_DOWN)) + wiiuse_set_ir(wm, 0); +
+ if (IS_JUST_PRESSED(wm, WIIMOTE_BUTTON_B))
+ wiiuse_toggle_rumble(wm); + + if (IS_JUST_PRESSED(wm, WIIMOTE_BUTTON_ONE)) { + int level; + WIIUSE_GET_IR_SENSITIVITY(wm, &level); + wiiuse_set_ir_sensitivity(wm, level+1); + } + if (IS_JUST_PRESSED(wm, WIIMOTE_BUTTON_TWO)) { + int level; + WIIUSE_GET_IR_SENSITIVITY(wm, &level); + wiiuse_set_ir_sensitivity(wm, level-1); + } + + #if 0 + if (IS_JUST_PRESSED(wm, WIIMOTE_BUTTON_A)) { + if (render_mode == IR) + render_mode = TEAPOT; + else + render_mode = IR; + resize_window(width, height); + } + #endif +} + +#define DRAW_TRIANGLE(x, y, z, s) do { \ + glVertex3f(x, y-s, z); \ + glVertex3f(x+s, y+s, z); \ + glVertex3f(x-s, y+s, z); \ + } while (0) + +int can_render() { + /* quick fps limit to ~60fps -- not too fancy, could be better */ + #ifdef WIN32 + if (GetTickCount() < (last_render + 16)) + return 0;
+ last_render = GetTickCount();
+ return 1; + #else + struct timeval now; + long elapsed_usec = 0; + + gettimeofday(&now, NULL); + + if (now.tv_usec > 1000000) { + now.tv_usec -= 1000000; + ++now.tv_sec; + } + + if (now.tv_sec > last_render.tv_sec) + elapsed_usec = ((now.tv_sec - last_render.tv_sec) * 1000000); + + if (now.tv_usec > last_render.tv_usec) + elapsed_usec += now.tv_usec - last_render.tv_usec; + else + elapsed_usec += last_render.tv_usec - now.tv_usec; + + if (time(NULL) > last_sec) { + printf("fps: %i\n", fps); + fps = 0; + last_sec = time(NULL); + } + + if (elapsed_usec < 16000) + return 0; + + last_render = now; + ++fps; + + return 1; + #endif +} + +void display() { + int i, wm; + float size = 5; + + if (!can_render()) + return;
+ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity();
+ + if (render_mode == IR) { + /* draw the IR stuff */ +
+ glDisable(GL_LIGHTING); + + glBegin(GL_TRIANGLES);
+ /* green center */
+ glColor3f(0.0, 1.0, 0.0);
+ DRAW_TRIANGLE(width/2, height/2, 0, size);
+ glEnd();
+
+ for (wm = 0; wm < MAX_WIIMOTES; ++wm) { + glBegin(GL_TRIANGLES); + /* red ir */ + glColor3f(1.0, 0.0, 0.0); + for (i = 0; i < 4; ++i) { + if (wiimotes[wm]->ir.dot[i].visible) + DRAW_TRIANGLE(wiimotes[wm]->ir.dot[i].rx, wiimotes[wm]->ir.dot[i].ry, 0, size); + } + + /* yellow corrected ir */ + glColor3f(1.0, 1.0, 0.0); + for (i = 0; i < 4; ++i) { + if (wiimotes[wm]->ir.dot[i].visible) + DRAW_TRIANGLE(wiimotes[wm]->ir.dot[i].x, wiimotes[wm]->ir.dot[i].y, 0, size); + } + + /* blue cursor */ + glColor3f(0.0, 0.0, 1.0); + DRAW_TRIANGLE(wiimotes[wm]->ir.x, wiimotes[wm]->ir.y-size, 0, size); + glEnd(); + } + } else { + /* draw the teapot */ + gluLookAt(0.0, 0.0, -5.0, + 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0); + + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + update_light(GL_LIGHT0, &light); + set_material(&red_plastic); + + glRotatef(wiimotes[0]->orient.roll, 0.0f, 0.0f, 1.0f); + glRotatef(wiimotes[0]->orient.pitch, 1.0f, 0.0f, 0.0f); + + + glutSolidTeapot(1); + }
+ + SDL_GL_SwapBuffers(); +} + + +void update_light(GLenum l, struct light_t* lptr) { + glLightfv(l, GL_POSITION, lptr->position); + glLightfv(l, GL_DIFFUSE, lptr->diffuse); + glLightfv(l, GL_SPECULAR, lptr->specular); + glLightfv(l, GL_AMBIENT, lptr->ambient); + glLightfv(l, GL_SPOT_DIRECTION, lptr->spotDirection); + glLightf(l, GL_SPOT_CUTOFF, lptr->spotCutoff); + glLightf(l, GL_SPOT_EXPONENT, lptr->spotExponent); + glLightf(l, GL_CONSTANT_ATTENUATION, lptr->spotAttenuation[0]); + glLightf(l, GL_LINEAR_ATTENUATION, lptr->spotAttenuation[1]); + glLightf(l, GL_QUADRATIC_ATTENUATION, lptr->spotAttenuation[2]); +} + + +void set_material(struct material_t* mptr) { + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, mptr->ambient); + glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, mptr->diffuse); + glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mptr->specular); + glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, mptr->shininess); + glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, mptr->emission); +} + + +void resize_window(GLint new_width, GLint new_height) {
+ int wm;
+ + width = new_width; + height = new_height; + + if (new_height == 0) + new_height = 1; + + SDL_SetVideoMode(width, height, 16, SDL_RESIZABLE | SDL_OPENGL); + + glViewport(0, 0, new_width, new_height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + if (render_mode == IR) + gluOrtho2D(0, width, height, 0); + else + gluPerspective(60.0f, (float)new_width/(float)new_height, 0.1f, 100.0f); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + width = new_width; + height = new_height; +
+ for (wm = 0; wm < MAX_WIIMOTES; ++wm)
+ wiiuse_set_ir_vres(wiimotes[wm], width, height); +} + +#ifndef WIN32 +int main(int argc, char** argv) { +#else +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { +#endif + int found, connected;
+ int wm; + + //printf("wiiuse version = %s\n", wiiuse_version()); + + wiimotes = wiiuse_init(MAX_WIIMOTES); + found = wiiuse_find(wiimotes, MAX_WIIMOTES, 5); + if (!found) + return 0; + connected = wiiuse_connect(wiimotes, MAX_WIIMOTES); + if (connected) + printf("Connected to %i wiimotes (of %i found).\n", connected, found); + else { + printf("Failed to connect to any wiimote.\n"); + return 0; + } + wiiuse_set_leds(wiimotes[0], WIIMOTE_LED_1 | WIIMOTE_LED_4); + wiiuse_set_leds(wiimotes[1], WIIMOTE_LED_2 | WIIMOTE_LED_4);
+ wiiuse_rumble(wiimotes[0], 1); + + #ifndef WIN32 + usleep(200000); + #else + Sleep(200); + #endif + + wiiuse_rumble(wiimotes[0], 0); +
+ /* enable IR and motion sensing for all wiimotes */
+ for (wm = 0; wm < MAX_WIIMOTES; ++wm) {
+ wiiuse_motion_sensing(wiimotes[wm], 1); + wiiuse_set_ir(wiimotes[wm], 1);
+ }
+ + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + printf("Failed to initialize SDL: %s\n", SDL_GetError()); + return 0; + } + + SDL_WM_SetCaption("wiiuse SDL IR Example", "wiiuse SDL IR Example");
+
+ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); + + /* set window size */ + width = wiimotes[0]->ir.vres[0]; + height = wiimotes[0]->ir.vres[1]; + SDL_SetVideoMode(width, height, 16, SDL_RESIZABLE | SDL_OPENGL);
+
+ for (wm = 0; wm < MAX_WIIMOTES; ++wm)
+ wiiuse_set_ir_vres(wiimotes[wm], width, height); + + /* set OpenGL stuff */ + glEnable(GL_DEPTH_TEST); + glEnable(GL_LIGHTING); + glEnable(GL_NORMALIZE); + glEnable(GL_BLEND); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthFunc(GL_LEQUAL); + glClearColor(0, 0, 0, 0); + + /* set the size of the window */ + resize_window(width, height); + + display();
+
+ #ifdef WIN32
+ last_render = GetTickCount(); + #endif + + while (1) { + SDL_Event event; +
+ if (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_VIDEORESIZE: + { + /* resize the window */ + resize_window(event.resize.w, event.resize.h); + break; + } + case SDL_QUIT: + { + /* shutdown */ + SDL_Quit(); + wiiuse_cleanup(wiimotes, MAX_WIIMOTES); + return 0; + } + default: + { + } + } + } + + if (wiiuse_poll(wiimotes, MAX_WIIMOTES)) { + /* + * This happens if something happened on any wiimote. + * So go through each one and check if anything happened. + */ + int i = 0; + for (; i < MAX_WIIMOTES; ++i) { + switch (wiimotes[i]->event) { + case WIIUSE_EVENT: + /* a generic event occured */ + handle_event(wiimotes[i]); + break; + + default: + break; + } + } + } + + display();
+ } +} diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/Makefile b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/Makefile new file mode 100644 index 0000000000..41b5cd1e65 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/Makefile @@ -0,0 +1,84 @@ +# +# wiiuse Makefile +# + +# +# Change this to your GCC version. +# +CC = gcc + +#################################################### +# +# You should not need to edit below this line. +# +#################################################### + +# +# Universal cflags +# +CFLAGS = -Wall -pipe -fPIC -funroll-loops + +ifeq ($(debug),1) + OBJ_PREFIX = debug + CFLAGS += -g -pg -DWITH_WIIUSE_DEBUG +else + OBJ_PREFIX = release + CFLAGS += -O2 +endif + +OBJ_DIR = $(OBJ_PREFIX)-$(shell $(CC) -v 2>&1|grep ^Target:|cut -d' ' -f2) + +# +# Linking flags +# +LDFLAGS = -L../src/$(OBJ_DIR) -lm -lwiiuse + +# +# Target binaries (always created as BIN) +# +BIN = ./$(OBJ_DIR)/wiiuse-example + +# +# Inclusion paths. +# +INCLUDES = -I../src/ + +# +# Generate a list of object files +# +OBJS = $(OBJ_DIR)/example.o + +############################### +# +# Build targets. +# +############################### + +all: $(BIN) + +clean: + @-rm $(OBJS) 2> /dev/null + +distclean: + @-rm -r debug-* release-* 2> /dev/null + +install: + @if [ -e $(BIN) ]; then \ + cp -v $(BIN) /usr/bin ; \ + fi + + +$(BIN): mkdir $(OBJS) + $(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) -o $(BIN) + +$(OBJ_DIR)/%.o: %.c + $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ + +mkdir: + @if [ ! -d $(OBJ_DIR) ]; then \ + mkdir $(OBJ_DIR); \ + fi + +run: all + LD_LIBRARY_PATH=`pwd`/../src/$(OBJ_DIR):$(LD_LIBRARY_PATH) $(BIN) + diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/example.c b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/example.c new file mode 100644 index 0000000000..8536cf683c --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/example.c @@ -0,0 +1,437 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + +/** + * @file + * + * @brief Example using the wiiuse API. + * + * This file is an example of how to use the wiiuse library. + */ + +#include <stdio.h> +#include <stdlib.h> + +#ifndef WIN32 + #include <unistd.h> +#endif + +#include "wiiuse.h" + + +#define MAX_WIIMOTES 4 + + +/** + * @brief Callback that handles an event. + * + * @param wm Pointer to a wiimote_t structure. + * + * This function is called automatically by the wiiuse library when an + * event occurs on the specified wiimote. + */ +void handle_event(struct wiimote_t* wm) { + printf("\n\n--- EVENT [id %i] ---\n", wm->unid); + + /* if a button is pressed, report it */ + if (IS_PRESSED(wm, WIIMOTE_BUTTON_A)) printf("A pressed\n"); + if (IS_PRESSED(wm, WIIMOTE_BUTTON_B)) printf("B pressed\n"); + if (IS_PRESSED(wm, WIIMOTE_BUTTON_UP)) printf("UP pressed\n"); + if (IS_PRESSED(wm, WIIMOTE_BUTTON_DOWN)) printf("DOWN pressed\n"); + if (IS_PRESSED(wm, WIIMOTE_BUTTON_LEFT)) printf("LEFT pressed\n"); + if (IS_PRESSED(wm, WIIMOTE_BUTTON_RIGHT)) printf("RIGHT pressed\n"); + if (IS_PRESSED(wm, WIIMOTE_BUTTON_MINUS)) printf("MINUS pressed\n"); + if (IS_PRESSED(wm, WIIMOTE_BUTTON_PLUS)) printf("PLUS pressed\n"); + if (IS_PRESSED(wm, WIIMOTE_BUTTON_ONE)) printf("ONE pressed\n"); + if (IS_PRESSED(wm, WIIMOTE_BUTTON_TWO)) printf("TWO pressed\n"); + if (IS_PRESSED(wm, WIIMOTE_BUTTON_HOME)) printf("HOME pressed\n"); + + /* + * Pressing minus will tell the wiimote we are no longer interested in movement. + * This is useful because it saves battery power. + */ + if (IS_JUST_PRESSED(wm, WIIMOTE_BUTTON_MINUS)) + wiiuse_motion_sensing(wm, 0); + + /* + * Pressing plus will tell the wiimote we are interested in movement. + */ + if (IS_JUST_PRESSED(wm, WIIMOTE_BUTTON_PLUS)) + wiiuse_motion_sensing(wm, 1); + + /* + * Pressing B will toggle the rumble + * + * if B is pressed but is not held, toggle the rumble + */ + if (IS_JUST_PRESSED(wm, WIIMOTE_BUTTON_B)) + wiiuse_toggle_rumble(wm); + + if (IS_JUST_PRESSED(wm, WIIMOTE_BUTTON_UP)) + wiiuse_set_ir(wm, 1); + if (IS_JUST_PRESSED(wm, WIIMOTE_BUTTON_DOWN)) + wiiuse_set_ir(wm, 0); + + /* if the accelerometer is turned on then print angles */ + if (WIIUSE_USING_ACC(wm)) { + printf("wiimote roll = %f [%f]\n", wm->orient.roll, wm->orient.a_roll); + printf("wiimote pitch = %f [%f]\n", wm->orient.pitch, wm->orient.a_pitch); + printf("wiimote yaw = %f\n", wm->orient.yaw); + } + + /* + * If IR tracking is enabled then print the coordinates + * on the virtual screen that the wiimote is pointing to. + * + * Also make sure that we see at least 1 dot. + */ + if (WIIUSE_USING_IR(wm)) { + int i = 0; + + /* go through each of the 4 possible IR sources */ + for (; i < 4; ++i) { + /* check if the source is visible */ + if (wm->ir.dot[i].visible) + printf("IR source %i: (%u, %u)\n", i, wm->ir.dot[i].x, wm->ir.dot[i].y); + } + + printf("IR cursor: (%u, %u)\n", wm->ir.x, wm->ir.y); + printf("IR z distance: %f\n", wm->ir.z); + } + + /* show events specific to supported expansions */ + if (wm->exp.type == EXP_NUNCHUK) { + /* nunchuk */ + struct nunchuk_t* nc = (nunchuk_t*)&wm->exp.nunchuk; + + if (IS_PRESSED(nc, NUNCHUK_BUTTON_C)) printf("Nunchuk: C pressed\n"); + if (IS_PRESSED(nc, NUNCHUK_BUTTON_Z)) printf("Nunchuk: Z pressed\n"); + + printf("nunchuk roll = %f\n", nc->orient.roll); + printf("nunchuk pitch = %f\n", nc->orient.pitch); + printf("nunchuk yaw = %f\n", nc->orient.yaw); + + printf("nunchuk joystick angle: %f\n", nc->js.ang); + printf("nunchuk joystick magnitude: %f\n", nc->js.mag); + } else if (wm->exp.type == EXP_CLASSIC) { + /* classic controller */ + struct classic_ctrl_t* cc = (classic_ctrl_t*)&wm->exp.classic; + + if (IS_PRESSED(cc, CLASSIC_CTRL_BUTTON_ZL)) printf("Classic: ZL pressed\n"); + if (IS_PRESSED(cc, CLASSIC_CTRL_BUTTON_B)) printf("Classic: B pressed\n"); + if (IS_PRESSED(cc, CLASSIC_CTRL_BUTTON_Y)) printf("Classic: Y pressed\n"); + if (IS_PRESSED(cc, CLASSIC_CTRL_BUTTON_A)) printf("Classic: A pressed\n"); + if (IS_PRESSED(cc, CLASSIC_CTRL_BUTTON_X)) printf("Classic: X pressed\n"); + if (IS_PRESSED(cc, CLASSIC_CTRL_BUTTON_ZR)) printf("Classic: ZR pressed\n"); + if (IS_PRESSED(cc, CLASSIC_CTRL_BUTTON_LEFT)) printf("Classic: LEFT pressed\n"); + if (IS_PRESSED(cc, CLASSIC_CTRL_BUTTON_UP)) printf("Classic: UP pressed\n"); + if (IS_PRESSED(cc, CLASSIC_CTRL_BUTTON_RIGHT)) printf("Classic: RIGHT pressed\n"); + if (IS_PRESSED(cc, CLASSIC_CTRL_BUTTON_DOWN)) printf("Classic: DOWN pressed\n"); + if (IS_PRESSED(cc, CLASSIC_CTRL_BUTTON_FULL_L)) printf("Classic: FULL L pressed\n"); + if (IS_PRESSED(cc, CLASSIC_CTRL_BUTTON_MINUS)) printf("Classic: MINUS pressed\n"); + if (IS_PRESSED(cc, CLASSIC_CTRL_BUTTON_HOME)) printf("Classic: HOME pressed\n"); + if (IS_PRESSED(cc, CLASSIC_CTRL_BUTTON_PLUS)) printf("Classic: PLUS pressed\n"); + if (IS_PRESSED(cc, CLASSIC_CTRL_BUTTON_FULL_R)) printf("Classic: FULL R pressed\n"); + + printf("classic L button pressed: %f\n", cc->l_shoulder); + printf("classic R button pressed: %f\n", cc->r_shoulder); + printf("classic left joystick angle: %f\n", cc->ljs.ang); + printf("classic left joystick magnitude: %f\n", cc->ljs.mag); + printf("classic right joystick angle: %f\n", cc->rjs.ang); + printf("classic right joystick magnitude: %f\n", cc->rjs.mag); + } else if (wm->exp.type == EXP_GUITAR_HERO_3) { + /* guitar hero 3 guitar */ + struct guitar_hero_3_t* gh3 = (guitar_hero_3_t*)&wm->exp.gh3; + + if (IS_PRESSED(gh3, GUITAR_HERO_3_BUTTON_STRUM_UP)) printf("Guitar: Strum Up pressed\n"); + if (IS_PRESSED(gh3, GUITAR_HERO_3_BUTTON_STRUM_DOWN)) printf("Guitar: Strum Down pressed\n"); + if (IS_PRESSED(gh3, GUITAR_HERO_3_BUTTON_YELLOW)) printf("Guitar: Yellow pressed\n"); + if (IS_PRESSED(gh3, GUITAR_HERO_3_BUTTON_GREEN)) printf("Guitar: Green pressed\n"); + if (IS_PRESSED(gh3, GUITAR_HERO_3_BUTTON_BLUE)) printf("Guitar: Blue pressed\n"); + if (IS_PRESSED(gh3, GUITAR_HERO_3_BUTTON_RED)) printf("Guitar: Red pressed\n"); + if (IS_PRESSED(gh3, GUITAR_HERO_3_BUTTON_ORANGE)) printf("Guitar: Orange pressed\n"); + if (IS_PRESSED(gh3, GUITAR_HERO_3_BUTTON_PLUS)) printf("Guitar: Plus pressed\n"); + if (IS_PRESSED(gh3, GUITAR_HERO_3_BUTTON_MINUS)) printf("Guitar: Minus pressed\n"); + + printf("Guitar whammy bar: %f\n", gh3->whammy_bar); + printf("Guitar joystick angle: %f\n", gh3->js.ang); + printf("Guitar joystick magnitude: %f\n", gh3->js.mag); + } +} + + +/** + * @brief Callback that handles a read event. + * + * @param wm Pointer to a wiimote_t structure. + * @param data Pointer to the filled data block. + * @param len Length in bytes of the data block. + * + * This function is called automatically by the wiiuse library when + * the wiimote has returned the full data requested by a previous + * call to wiiuse_read_data(). + * + * You can read data on the wiimote, such as Mii data, if + * you know the offset address and the length. + * + * The \a data pointer was specified on the call to wiiuse_read_data(). + * At the time of this function being called, it is not safe to deallocate + * this buffer. + */ +void handle_read(struct wiimote_t* wm, byte* data, unsigned short len) { + int i = 0; + + printf("\n\n--- DATA READ [wiimote id %i] ---\n", wm->unid); + printf("finished read of size %i\n", len); + for (; i < len; ++i) { + if (!(i%16)) + printf("\n"); + printf("%x ", data[i]); + } + printf("\n\n"); +} + + +/** + * @brief Callback that handles a controller status event. + * + * @param wm Pointer to a wiimote_t structure. + * @param attachment Is there an attachment? (1 for yes, 0 for no) + * @param speaker Is the speaker enabled? (1 for yes, 0 for no) + * @param ir Is the IR support enabled? (1 for yes, 0 for no) + * @param led What LEDs are lit. + * @param battery_level Battery level, between 0.0 (0%) and 1.0 (100%). + * + * This occurs when either the controller status changed + * or the controller status was requested explicitly by + * wiiuse_status(). + * + * One reason the status can change is if the nunchuk was + * inserted or removed from the expansion port. + */ +void handle_ctrl_status(struct wiimote_t* wm) { + printf("\n\n--- CONTROLLER STATUS [wiimote id %i] ---\n", wm->unid); + + printf("attachment: %i\n", wm->exp.type); + printf("speaker: %i\n", WIIUSE_USING_SPEAKER(wm)); + printf("ir: %i\n", WIIUSE_USING_IR(wm)); + printf("leds: %i %i %i %i\n", WIIUSE_IS_LED_SET(wm, 1), WIIUSE_IS_LED_SET(wm, 2), WIIUSE_IS_LED_SET(wm, 3), WIIUSE_IS_LED_SET(wm, 4)); + printf("battery: %f %%\n", wm->battery_level); +} + + +/** + * @brief Callback that handles a disconnection event. + * + * @param wm Pointer to a wiimote_t structure. + * + * This can happen if the POWER button is pressed, or + * if the connection is interrupted. + */ +void handle_disconnect(wiimote* wm) { + printf("\n\n--- DISCONNECTED [wiimote id %i] ---\n", wm->unid); +} + + +void test(struct wiimote_t* wm, byte* data, unsigned short len) { + printf("test: %i [%x %x %x %x]\n", len, data[0], data[1], data[2], data[3]); +} + + + +/** + * @brief main() + * + * Connect to up to two wiimotes and print any events + * that occur on either device. + */ +int main(int argc, char** argv) { + wiimote** wiimotes; + int found, connected; + + /* + * Initialize an array of wiimote objects. + * + * The parameter is the number of wiimotes I want to create. + */ + wiimotes = wiiuse_init(MAX_WIIMOTES); + + /* + * Find wiimote devices + * + * Now we need to find some wiimotes. + * Give the function the wiimote array we created, and tell it there + * are MAX_WIIMOTES wiimotes we are interested in. + * + * Set the timeout to be 5 seconds. + * + * This will return the number of actual wiimotes that are in discovery mode. + */ + found = wiiuse_find(wiimotes, MAX_WIIMOTES, 5); + if (!found) { + printf ("No wiimotes found."); + return 0; + } + + /* + * Connect to the wiimotes + * + * Now that we found some wiimotes, connect to them. + * Give the function the wiimote array and the number + * of wiimote devices we found. + * + * This will return the number of established connections to the found wiimotes. + */ + connected = wiiuse_connect(wiimotes, MAX_WIIMOTES); + if (connected) + printf("Connected to %i wiimotes (of %i found).\n", connected, found); + else { + printf("Failed to connect to any wiimote.\n"); + return 0; + } + + /* + * Now set the LEDs and rumble for a second so it's easy + * to tell which wiimotes are connected (just like the wii does). + */ + wiiuse_set_leds(wiimotes[0], WIIMOTE_LED_1); + wiiuse_set_leds(wiimotes[1], WIIMOTE_LED_2); + wiiuse_set_leds(wiimotes[2], WIIMOTE_LED_3); + wiiuse_set_leds(wiimotes[3], WIIMOTE_LED_4); + wiiuse_rumble(wiimotes[0], 1); + wiiuse_rumble(wiimotes[1], 1); + + #ifndef WIN32 + usleep(200000); + #else + Sleep(200); + #endif + + wiiuse_rumble(wiimotes[0], 0); + wiiuse_rumble(wiimotes[1], 0); + + /* + * Maybe I'm interested in the battery power of the 0th + * wiimote. This should be WIIMOTE_ID_1 but to be sure + * you can get the wiimote assoicated with WIIMOTE_ID_1 + * using the wiiuse_get_by_id() function. + * + * A status request will return other things too, like + * if any expansions are plugged into the wiimote or + * what LEDs are lit. + */ + //wiiuse_status(wiimotes[0]); + + /* + * This is the main loop + * + * wiiuse_poll() needs to be called with the wiimote array + * and the number of wiimote structures in that array + * (it doesn't matter if some of those wiimotes are not used + * or are not connected). + * + * This function will set the event flag for each wiimote + * when the wiimote has things to report. + */ + while (1) { + if (wiiuse_poll(wiimotes, MAX_WIIMOTES)) { + /* + * This happens if something happened on any wiimote. + * So go through each one and check if anything happened. + */ + int i = 0; + for (; i < MAX_WIIMOTES; ++i) { + switch (wiimotes[i]->event) { + case WIIUSE_EVENT: + /* a generic event occured */ + handle_event(wiimotes[i]); + break; + + case WIIUSE_STATUS: + /* a status event occured */ + handle_ctrl_status(wiimotes[i]); + break; + + case WIIUSE_DISCONNECT: + case WIIUSE_UNEXPECTED_DISCONNECT: + /* the wiimote disconnected */ + handle_disconnect(wiimotes[i]); + break; + + case WIIUSE_READ_DATA: + /* + * Data we requested to read was returned. + * Take a look at wiimotes[i]->read_req + * for the data. + */ + break; + + case WIIUSE_NUNCHUK_INSERTED: + /* + * a nunchuk was inserted + * This is a good place to set any nunchuk specific + * threshold values. By default they are the same + * as the wiimote. + */ + //wiiuse_set_nunchuk_orient_threshold((struct nunchuk_t*)&wiimotes[i]->exp.nunchuk, 90.0f); + //wiiuse_set_nunchuk_accel_threshold((struct nunchuk_t*)&wiimotes[i]->exp.nunchuk, 100); + printf("Nunchuk inserted.\n"); + break; + + case WIIUSE_CLASSIC_CTRL_INSERTED: + printf("Classic controller inserted.\n"); + break; + + case WIIUSE_GUITAR_HERO_3_CTRL_INSERTED: + /* some expansion was inserted */ + handle_ctrl_status(wiimotes[i]); + printf("Guitar Hero 3 controller inserted.\n"); + break; + + case WIIUSE_NUNCHUK_REMOVED: + case WIIUSE_CLASSIC_CTRL_REMOVED: + case WIIUSE_GUITAR_HERO_3_CTRL_REMOVED: + /* some expansion was removed */ + handle_ctrl_status(wiimotes[i]); + printf("An expansion was removed.\n"); + break; + + default: + break; + } + } + } + } + + /* + * Disconnect the wiimotes + */ + wiiuse_cleanup(wiimotes, MAX_WIIMOTES); + + return 0; +} diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/msvc/wiiuseexample.dsp b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/msvc/wiiuseexample.dsp new file mode 100644 index 0000000000..9e9b34faa8 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/msvc/wiiuseexample.dsp @@ -0,0 +1,106 @@ +# Microsoft Developer Studio Project File - Name="wiiuseexample" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=wiiuseexample - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "wiiuseexample.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "wiiuseexample.mak" CFG="wiiuseexample - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "wiiuseexample - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "wiiuseexample - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "wiiuseexample - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /W3 /GX /O2 /I "..\..\src" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib wiiuse.lib /nologo /subsystem:console /machine:I386
+
+!ELSEIF "$(CFG)" == "wiiuseexample - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\src" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "wiiuseexample - Win32 Release"
+# Name "wiiuseexample - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=..\example.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE=..\..\src\wiiuse.h
+# End Source File
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/msvc/wiiuseexample.dsw b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/msvc/wiiuseexample.dsw new file mode 100644 index 0000000000..e31f8f5615 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/msvc/wiiuseexample.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00
+# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
+
+###############################################################################
+
+Project: "wiiuseexample"=".\wiiuseexample.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Global:
+
+Package=<5>
+{{{
+}}}
+
+Package=<3>
+{{{
+}}}
+
+###############################################################################
+
diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/msvc/wiiuseexample.opt b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/msvc/wiiuseexample.opt Binary files differnew file mode 100644 index 0000000000..fe837f89e2 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/msvc/wiiuseexample.opt diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/Makefile b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/Makefile new file mode 100644 index 0000000000..bab5b140f4 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/Makefile @@ -0,0 +1,91 @@ +# +# wiiuse Makefile +# + +# +# Change this to your GCC version. +# +CC = gcc + +#################################################### +# +# You should not need to edit below this line. +# +#################################################### + +# +# Universal cflags +# +CFLAGS = -Wall -pipe -fPIC -funroll-loops + +ifeq ($(debug),1) + OBJ_PREFIX = debug + CFLAGS += -g -pg -DWITH_WIIUSE_DEBUG + +else + OBJ_PREFIX = release + CFLAGS += -O2 +endif + +OBJ_DIR = $(OBJ_PREFIX)-$(shell $(CC) -v 2>&1|grep ^Target:|cut -d' ' -f2) + +# +# Linking flags +# +LDFLAGS = -shared -lm -lbluetooth + +# +# Target binaries (always created as BIN) +# +BIN = ./$(OBJ_DIR)/libwiiuse.so + +# +# Inclusion paths. +# +INCLUDES = -I. + +# +# Generate a list of object files +# +OBJS = \ + $(OBJ_DIR)/classic.o \ + $(OBJ_DIR)/dynamics.o \ + $(OBJ_DIR)/events.o \ + $(OBJ_DIR)/io.o \ + $(OBJ_DIR)/io_nix.o \ + $(OBJ_DIR)/ir.o \ + $(OBJ_DIR)/nunchuk.o \ + $(OBJ_DIR)/guitar_hero_3.o \ + $(OBJ_DIR)/wiiuse.o + +############################### +# +# Build targets. +# +############################### + +all: $(BIN) + +clean: + @-rm $(OBJS) 2> /dev/null + +distclean: + @-rm -r debug-* release-* 2> /dev/null + +install: + @if [ -e $(BIN) ]; then \ + cp -v $(BIN) /usr/lib ; \ + fi + @cp -v wiiuse.h /usr/include + +$(BIN): mkdir $(OBJS) + $(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) -o $(BIN) + +$(OBJ_DIR)/%.o: %.c + $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ + +mkdir: + @if [ ! -d $(OBJ_DIR) ]; then \ + mkdir $(OBJ_DIR); \ + fi + diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/classic.c b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/classic.c new file mode 100644 index 0000000000..1d2c3ab275 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/classic.c @@ -0,0 +1,190 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + +/** + * @file + * @brief Classic controller expansion device. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> + +#ifdef WIN32 + #include <Winsock2.h> +#endif + +#include "definitions.h" +#include "wiiuse_internal.h" +#include "dynamics.h" +#include "events.h" +#include "classic.h" + +static void classic_ctrl_pressed_buttons(struct classic_ctrl_t* cc, short now); + +/** + * @brief Handle the handshake data from the classic controller. + * + * @param cc A pointer to a classic_ctrl_t structure. + * @param data The data read in from the device. + * @param len The length of the data block, in bytes. + * + * @return Returns 1 if handshake was successful, 0 if not. + */ +int classic_ctrl_handshake(struct wiimote_t* wm, struct classic_ctrl_t* cc, byte* data, unsigned short len) { + int i;
+ int offset = 0; + + cc->btns = 0; + cc->btns_held = 0; + cc->btns_released = 0; + cc->r_shoulder = 0; + cc->l_shoulder = 0; + + /* decrypt data */ + for (i = 0; i < len; ++i) + data[i] = (data[i] ^ 0x17) + 0x17; +
+ if (data[offset] == 0xFF) {
+ /*
+ * Sometimes the data returned here is not correct.
+ * This might happen because the wiimote is lagging
+ * behind our initialization sequence.
+ * To fix this just request the handshake again.
+ *
+ * Other times it's just the first 16 bytes are 0xFF,
+ * but since the next 16 bytes are the same, just use
+ * those.
+ */
+ if (data[offset + 16] == 0xFF) {
+ /* get the calibration data */
+ byte* handshake_buf = malloc(EXP_HANDSHAKE_LEN * sizeof(byte));
+
+ WIIUSE_DEBUG("Classic controller handshake appears invalid, trying again.");
+ wiiuse_read_data_cb(wm, handshake_expansion, handshake_buf, WM_EXP_MEM_CALIBR, EXP_HANDSHAKE_LEN);
+
+ return 0;
+ } else
+ offset += 16;
+ }
+
+ + /* joystick stuff */ + cc->ljs.max.x = data[0 + offset] / 4; + cc->ljs.min.x = data[1 + offset] / 4; + cc->ljs.center.x = data[2 + offset] / 4; + cc->ljs.max.y = data[3 + offset] / 4; + cc->ljs.min.y = data[4 + offset] / 4; + cc->ljs.center.y = data[5 + offset] / 4; + + cc->rjs.max.x = data[6 + offset] / 8; + cc->rjs.min.x = data[7 + offset] / 8; + cc->rjs.center.x = data[8 + offset] / 8; + cc->rjs.max.y = data[9 + offset] / 8; + cc->rjs.min.y = data[10 + offset] / 8; + cc->rjs.center.y = data[11 + offset] / 8; + + /* handshake done */ + wm->exp.type = EXP_CLASSIC; + + #ifdef WIN32 + wm->timeout = WIIMOTE_DEFAULT_TIMEOUT; + #endif + + return 1; +} + + +/** + * @brief The classic controller disconnected. + * + * @param cc A pointer to a classic_ctrl_t structure. + */ +void classic_ctrl_disconnected(struct classic_ctrl_t* cc) { + memset(cc, 0, sizeof(struct classic_ctrl_t)); +} + + + +/** + * @brief Handle classic controller event. + * + * @param cc A pointer to a classic_ctrl_t structure. + * @param msg The message specified in the event packet. + */ +void classic_ctrl_event(struct classic_ctrl_t* cc, byte* msg) { + int i, lx, ly, rx, ry; + byte l, r; + + /* decrypt data */ + for (i = 0; i < 6; ++i) + msg[i] = (msg[i] ^ 0x17) + 0x17; + + classic_ctrl_pressed_buttons(cc, BIG_ENDIAN_SHORT(*(short*)(msg + 4))); + + /* left/right buttons */ + l = (((msg[2] & 0x60) >> 2) | ((msg[3] & 0xE0) >> 5)); + r = (msg[3] & 0x1F); + + /* + * TODO - LR range hardcoded from 0x00 to 0x1F. + * This is probably in the calibration somewhere. + */ + cc->r_shoulder = ((float)r / 0x1F); + cc->l_shoulder = ((float)l / 0x1F); + + /* calculate joystick orientation */ + lx = (msg[0] & 0x3F); + ly = (msg[1] & 0x3F); + rx = ((msg[0] & 0xC0) >> 3) | ((msg[1] & 0xC0) >> 5) | ((msg[2] & 0x80) >> 7); + ry = (msg[2] & 0x1F); + + calc_joystick_state(&cc->ljs, lx, ly); + calc_joystick_state(&cc->rjs, rx, ry); +} + + +/** + * @brief Find what buttons are pressed. + * + * @param cc A pointer to a classic_ctrl_t structure. + * @param msg The message byte specified in the event packet. + */ +static void classic_ctrl_pressed_buttons(struct classic_ctrl_t* cc, short now) { + /* message is inverted (0 is active, 1 is inactive) */ + now = ~now & CLASSIC_CTRL_BUTTON_ALL; + + /* pressed now & were pressed, then held */ + cc->btns_held = (now & cc->btns); + + /* were pressed or were held & not pressed now, then released */ + cc->btns_released = ((cc->btns | cc->btns_held) & ~now); + + /* buttons pressed now */ + cc->btns = now; +} diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/classic.h b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/classic.h new file mode 100644 index 0000000000..356f6a458b --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/classic.h @@ -0,0 +1,53 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + +/** + * @file + * @brief Classic controller expansion device. + */ + +#ifndef CLASSIC_H_INCLUDED +#define CLASSIC_H_INCLUDED + +#include "wiiuse_internal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int classic_ctrl_handshake(struct wiimote_t* wm, struct classic_ctrl_t* cc, byte* data, unsigned short len); + +void classic_ctrl_disconnected(struct classic_ctrl_t* cc); + +void classic_ctrl_event(struct classic_ctrl_t* cc, byte* msg); + +#ifdef __cplusplus +} +#endif + +#endif // CLASSIC_H_INCLUDED diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/definitions.h b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/definitions.h new file mode 100644 index 0000000000..5a8da85def --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/definitions.h @@ -0,0 +1,79 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + +/** + * @file + * @brief General definitions. + */ + +#ifndef DEFINITIONS_H_INCLUDED +#define DEFINITIONS_H_INCLUDED + +/* this is wiiuse - used to distinguish from third party programs using wiiuse.h */ +#include "os.h" + +#define WIIMOTE_PI 3.14159265f + +//#define WITH_WIIUSE_DEBUG + +/* Error output macros */ +#define WIIUSE_ERROR(fmt, ...) fprintf(stderr, "[ERROR] " fmt "\n", ##__VA_ARGS__) + +/* Warning output macros */ +#define WIIUSE_WARNING(fmt, ...) fprintf(stderr, "[WARNING] " fmt "\n", ##__VA_ARGS__) + +/* Information output macros */ +#define WIIUSE_INFO(fmt, ...) fprintf(stderr, "[INFO] " fmt "\n", ##__VA_ARGS__) + +#ifdef WITH_WIIUSE_DEBUG
+ #ifdef WIN32
+ #define WIIUSE_DEBUG(fmt, ...) do { \
+ char* file = __FILE__; \
+ int i = strlen(file) - 1; \
+ for (; i && (file[i] != '\\'); --i); \
+ fprintf(stderr, "[DEBUG] %s:%i: " fmt "\n", file+i+1, __LINE__, ##__VA_ARGS__); \
+ } while (0)
+ #else + #define WIIUSE_DEBUG(fmt, ...) fprintf(stderr, "[DEBUG] " __FILE__ ":%i: " fmt "\n", __LINE__, ##__VA_ARGS__)
+ #endif +#else + #define WIIUSE_DEBUG(fmt, ...) +#endif + +/* Convert between radians and degrees */ +#define RAD_TO_DEGREE(r) ((r * 180.0f) / WIIMOTE_PI) +#define DEGREE_TO_RAD(d) (d * (WIIMOTE_PI / 180.0f)) + +/* Convert to big endian */ +#define BIG_ENDIAN_LONG(i) (htonl(i)) +#define BIG_ENDIAN_SHORT(i) (htons(i)) + +#define absf(x) ((x >= 0) ? (x) : (x * -1.0f)) +#define diff_f(x, y) ((x >= y) ? (absf(x - y)) : (absf(y - x))) + +#endif // DEFINITIONS_H_INCLUDED diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/dynamics.c b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/dynamics.c new file mode 100644 index 0000000000..53612a6b90 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/dynamics.c @@ -0,0 +1,228 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + +/** + * @file + * @brief Handles the dynamics of the wiimote. + * + * The file includes functions that handle the dynamics + * of the wiimote. Such dynamics include orientation and + * motion sensing. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> + +#ifdef WIN32 + #include <float.h> +#endif + +#include "definitions.h" +#include "wiiuse_internal.h" +#include "ir.h" +#include "dynamics.h" + +/** + * @brief Calculate the roll, pitch, yaw. + * + * @param ac An accelerometer (accel_t) structure. + * @param accel [in] Pointer to a vec3b_t structure that holds the raw acceleration data. + * @param orient [out] Pointer to a orient_t structure that will hold the orientation data. + * @param rorient [out] Pointer to a orient_t structure that will hold the non-smoothed orientation data. + * @param smooth If smoothing should be performed on the angles calculated. 1 to enable, 0 to disable. + * + * Given the raw acceleration data from the accelerometer struct, calculate + * the orientation of the device and set it in the \a orient parameter. + */ +void calculate_orientation(struct accel_t* ac, struct vec3b_t* accel, struct orient_t* orient, int smooth) { + float xg, yg, zg; + float x, y, z; + + /* + * roll - use atan(z / x) [ ranges from -180 to 180 ] + * pitch - use atan(z / y) [ ranges from -180 to 180 ] + * yaw - impossible to tell without IR + */ + + /* yaw - set to 0, IR will take care of it if it's enabled */ + orient->yaw = 0.0f; + + /* find out how much it has to move to be 1g */ + xg = (float)ac->cal_g.x; + yg = (float)ac->cal_g.y; + zg = (float)ac->cal_g.z; + + /* find out how much it actually moved and normalize to +/- 1g */ + x = ((float)accel->x - (float)ac->cal_zero.x) / xg; + y = ((float)accel->y - (float)ac->cal_zero.y) / yg; + z = ((float)accel->z - (float)ac->cal_zero.z) / zg; + + /* make sure x,y,z are between -1 and 1 for the tan functions */ + if (x < -1.0f) x = -1.0f; + else if (x > 1.0f) x = 1.0f; + if (y < -1.0f) y = -1.0f; + else if (y > 1.0f) y = 1.0f; + if (z < -1.0f) z = -1.0f; + else if (z > 1.0f) z = 1.0f; + + /* if it is over 1g then it is probably accelerating and not reliable */ + if (abs(accel->x - ac->cal_zero.x) <= ac->cal_g.x) { + /* roll */ + x = RAD_TO_DEGREE(atan2f(x, z)); + + orient->roll = x; + orient->a_roll = x; + } + + if (abs(accel->y - ac->cal_zero.y) <= ac->cal_g.y) { + /* pitch */ + y = RAD_TO_DEGREE(atan2f(y, z)); + + orient->pitch = y; + orient->a_pitch = y; + } +
+ /* smooth the angles if enabled */
+ if (smooth) {
+ apply_smoothing(ac, orient, SMOOTH_ROLL);
+ apply_smoothing(ac, orient, SMOOTH_PITCH);
+ }
+} + + +/** + * @brief Calculate the gravity forces on each axis. + * + * @param ac An accelerometer (accel_t) structure. + * @param accel [in] Pointer to a vec3b_t structure that holds the raw acceleration data. + * @param gforce [out] Pointer to a gforce_t structure that will hold the gravity force data. + */ +void calculate_gforce(struct accel_t* ac, struct vec3b_t* accel, struct gforce_t* gforce) { + float xg, yg, zg; + + /* find out how much it has to move to be 1g */ + xg = (float)ac->cal_g.x; + yg = (float)ac->cal_g.y; + zg = (float)ac->cal_g.z; + + /* find out how much it actually moved and normalize to +/- 1g */ + gforce->x = ((float)accel->x - (float)ac->cal_zero.x) / xg; + gforce->y = ((float)accel->y - (float)ac->cal_zero.y) / yg; + gforce->z = ((float)accel->z - (float)ac->cal_zero.z) / zg; +} + + +/** + * @brief Calculate the angle and magnitude of a joystick. + * + * @param js [out] Pointer to a joystick_t structure. + * @param x The raw x-axis value. + * @param y The raw y-axis value. + */ +void calc_joystick_state(struct joystick_t* js, float x, float y) { + float rx, ry, ang; + + /* + * Since the joystick center may not be exactly: + * (min + max) / 2 + * Then the range from the min to the center and the center to the max + * may be different. + * Because of this, depending on if the current x or y value is greater + * or less than the assoicated axis center value, it needs to be interpolated + * between the center and the minimum or maxmimum rather than between + * the minimum and maximum. + * + * So we have something like this: + * (x min) [-1] ---------*------ [0] (x center) [0] -------- [1] (x max) + * Where the * is the current x value. + * The range is therefore -1 to 1, 0 being the exact center rather than + * the middle of min and max. + */ + if (x == js->center.x) + rx = 0; + else if (x >= js->center.x) + rx = ((float)(x - js->center.x) / (float)(js->max.x - js->center.x)); + else + rx = ((float)(x - js->min.x) / (float)(js->center.x - js->min.x)) - 1.0f; + + if (y == js->center.y) + ry = 0; + else if (y >= js->center.y) + ry = ((float)(y - js->center.y) / (float)(js->max.y - js->center.y)); + else + ry = ((float)(y - js->min.y) / (float)(js->center.y - js->min.y)) - 1.0f; + + /* calculate the joystick angle and magnitude */ + ang = RAD_TO_DEGREE(atanf(ry / rx)); + ang -= 90.0f; + if (rx < 0.0f) + ang -= 180.0f; + js->ang = absf(ang); + js->mag = (float) sqrt((rx * rx) + (ry * ry)); +} + + +void apply_smoothing(struct accel_t* ac, struct orient_t* orient, int type) { + switch (type) { + case SMOOTH_ROLL: + {
+ /* it's possible last iteration was nan or inf, so set it to 0 if that happened */ + if (isnan(ac->st_roll) || isinf(ac->st_roll)) + ac->st_roll = 0.0f; + + /* + * If the sign changes (which will happen if going from -180 to 180) + * or from (-1 to 1) then don't smooth, just use the new angle. + */ + if (((ac->st_roll < 0) && (orient->roll > 0)) || ((ac->st_roll > 0) && (orient->roll < 0))) { + ac->st_roll = orient->roll; + } else { + orient->roll = ac->st_roll + (ac->st_alpha * (orient->a_roll - ac->st_roll)); + ac->st_roll = orient->roll; + } + + return; + } + + case SMOOTH_PITCH: + { + if (isnan(ac->st_pitch) || isinf(ac->st_pitch)) + ac->st_pitch = 0.0f; + + if (((ac->st_pitch < 0) && (orient->pitch > 0)) || ((ac->st_pitch > 0) && (orient->pitch < 0))) { + ac->st_pitch = orient->pitch; + } else { + orient->pitch = ac->st_pitch + (ac->st_alpha * (orient->a_pitch - ac->st_pitch)); + ac->st_pitch = orient->pitch; + } + + return; + } + } +} diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/dynamics.h b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/dynamics.h new file mode 100644 index 0000000000..2a8f96500b --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/dynamics.h @@ -0,0 +1,56 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + +/** + * @file + * @brief Handles the dynamics of the wiimote. + * + * The file includes functions that handle the dynamics + * of the wiimote. Such dynamics include orientation and + * motion sensing. + */ + +#ifndef DYNAMICS_H_INCLUDED +#define DYNAMICS_H_INCLUDED + +#include "wiiuse_internal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void calculate_orientation(struct accel_t* ac, struct vec3b_t* accel, struct orient_t* orient, int smooth); +void calculate_gforce(struct accel_t* ac, struct vec3b_t* accel, struct gforce_t* gforce); +void calc_joystick_state(struct joystick_t* js, float x, float y); +void apply_smoothing(struct accel_t* ac, struct orient_t* orient, int type); + +#ifdef __cplusplus +} +#endif + +#endif // DYNAMICS_H_INCLUDED diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/events.c b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/events.c new file mode 100644 index 0000000000..6668eda5c8 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/events.c @@ -0,0 +1,878 @@ +/*
+ * wiiuse
+ *
+ * Written By:
+ * Michael Laforest < para >
+ * Email: < thepara (--AT--) g m a i l [--DOT--] com >
+ *
+ * Copyright 2006-2007
+ *
+ * This file is part of wiiuse.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * $Header$
+ *
+ */
+
+/**
+ * @file
+ * @brief Handles wiimote events.
+ *
+ * The file includes functions that handle the events
+ * that are sent from the wiimote to us.
+ */
+
+#include <stdio.h>
+
+#ifndef WIN32
+ #include <sys/time.h>
+ #include <unistd.h>
+ #include <errno.h> +#else
+ #include <winsock2.h> +#endif
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "definitions.h"
+#include "io.h"
+#include "wiiuse_internal.h"
+#include "dynamics.h"
+#include "ir.h"
+#include "nunchuk.h"
+#include "classic.h"
+#include "guitar_hero_3.h"
+#include "events.h"
+
+static void idle_cycle(struct wiimote_t* wm);
+static void clear_dirty_reads(struct wiimote_t* wm); +static void propagate_event(struct wiimote_t* wm, byte event, byte* msg);
+static void event_data_read(struct wiimote_t* wm, byte* msg);
+static void event_status(struct wiimote_t* wm, byte* msg);
+static void handle_expansion(struct wiimote_t* wm, byte* msg);
+
+static void save_state(struct wiimote_t* wm);
+static int state_changed(struct wiimote_t* wm);
+
+/**
+ * @brief Poll the wiimotes for any events.
+ *
+ * @param wm An array of pointers to wiimote_t structures.
+ * @param wiimotes The number of wiimote_t structures in the \a wm array.
+ *
+ * @return Returns number of wiimotes that an event has occured on.
+ *
+ * It is necessary to poll the wiimote devices for events
+ * that occur. If an event occurs on a particular wiimote,
+ * the event variable will be set.
+ */
+int wiiuse_poll(struct wiimote_t** wm, int wiimotes) {
+ int evnt = 0;
+
+ #ifndef WIN32
+ /*
+ * *nix
+ */
+ struct timeval tv;
+ fd_set fds;
+ int r;
+ int i;
+ int highest_fd = -1;
+
+ if (!wm) return 0;
+
+ /* block select() for 1/2000th of a second */
+ tv.tv_sec = 0;
+ tv.tv_usec = 500;
+
+ FD_ZERO(&fds);
+
+ for (i = 0; i < wiimotes; ++i) {
+ /* only poll it if it is connected */
+ if (WIIMOTE_IS_SET(wm[i], WIIMOTE_STATE_CONNECTED)) {
+ FD_SET(wm[i]->in_sock, &fds);
+
+ /* find the highest fd of the connected wiimotes */
+ if (wm[i]->in_sock > highest_fd)
+ highest_fd = wm[i]->in_sock;
+ }
+
+ wm[i]->event = WIIUSE_NONE;
+ }
+
+ if (highest_fd == -1)
+ /* nothing to poll */
+ return 0;
+
+ if (select(highest_fd + 1, &fds, NULL, NULL, &tv) == -1) {
+ WIIUSE_ERROR("Unable to select() the wiimote interrupt socket(s).");
+ perror("Error Details");
+ return 0;
+ }
+
+ /* check each socket for an event */
+ for (i = 0; i < wiimotes; ++i) {
+ /* if this wiimote is not connected, skip it */
+ if (!WIIMOTE_IS_CONNECTED(wm[i]))
+ continue;
+
+ if (FD_ISSET(wm[i]->in_sock, &fds)) {
+ /* clear out the event buffer */
+ memset(wm[i]->event_buf, 0, sizeof(wm[i]->event_buf)); + + /* clear out any old read requests */ + clear_dirty_reads(wm[i]);
+
+ /* read the pending message into the buffer */
+ r = read(wm[i]->in_sock, wm[i]->event_buf, sizeof(wm[i]->event_buf));
+ if (r == -1) {
+ /* error reading data */
+ WIIUSE_ERROR("Receiving wiimote data (id %i).", wm[i]->unid); + perror("Error Details"); + + if (errno == ENOTCONN) { + /* this can happen if the bluetooth dongle is disconnected */ + WIIUSE_ERROR("Bluetooth appears to be disconnected. Wiimote unid %i will be disconnected.", wm[i]->unid); + wiiuse_disconnect(wm[i]); + wm[i]->event = WIIUSE_UNEXPECTED_DISCONNECT; + } +
+ continue;
+ }
+ if (!r) {
+ /* remote disconnect */
+ wiiuse_disconnected(wm[i]);
+ evnt = 1;
+ continue;
+ }
+
+ /* propagate the event */
+ propagate_event(wm[i], wm[i]->event_buf[1], wm[i]->event_buf+2);
+ evnt += (wm[i]->event != WIIUSE_NONE);
+ } else {
+ idle_cycle(wm[i]);
+ }
+ }
+ #else
+ /*
+ * Windows
+ */
+ int i;
+
+ if (!wm) return 0;
+
+ for (i = 0; i < wiimotes; ++i) {
+ wm[i]->event = WIIUSE_NONE;
+
+ if (wiiuse_io_read(wm[i])) {
+ /* propagate the event */
+ propagate_event(wm[i], wm[i]->event_buf[0], wm[i]->event_buf+1);
+ evnt += (wm[i]->event != WIIUSE_NONE);
+
+ /* clear out the event buffer */
+ memset(wm[i]->event_buf, 0, sizeof(wm[i]->event_buf));
+ } else {
+ idle_cycle(wm[i]);
+ }
+ }
+ #endif
+
+ return evnt;
+}
+
+
+/**
+ * @brief Called on a cycle where no significant change occurs.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ */
+static void idle_cycle(struct wiimote_t* wm) {
+ /*
+ * Smooth the angles.
+ *
+ * This is done to make sure that on every cycle the orientation
+ * angles are smoothed. Normally when an event occurs the angles
+ * are updated and smoothed, but if no packet comes in then the
+ * angles remain the same. This means the angle wiiuse reports
+ * is still an old value. Smoothing needs to be applied in this
+ * case in order for the angle it reports to converge to the true
+ * angle of the device.
+ */
+ if (WIIUSE_USING_ACC(wm) && WIIMOTE_IS_FLAG_SET(wm, WIIUSE_SMOOTHING)) {
+ apply_smoothing(&wm->accel_calib, &wm->orient, SMOOTH_ROLL);
+ apply_smoothing(&wm->accel_calib, &wm->orient, SMOOTH_PITCH);
+ }
+ + /* clear out any old read requests */ + clear_dirty_reads(wm); +} + + +/** + * @brief Clear out all old 'dirty' read requests. + * + * @param wm Pointer to a wiimote_t structure. + */ +static void clear_dirty_reads(struct wiimote_t* wm) { + struct read_req_t* req = wm->read_req; + + while (req && req->dirty) { + WIIUSE_DEBUG("Cleared old read request for address: %x", req->addr); + + wm->read_req = req->next; + free(req); + req = wm->read_req; + } +}
+
+
+/**
+ * @brief Analyze the event that occured on a wiimote.
+ *
+ * @param wm An array of pointers to wiimote_t structures.
+ * @param event The event that occured.
+ * @param msg The message specified in the event packet.
+ *
+ * Pass the event to the registered event callback.
+ */
+static void propagate_event(struct wiimote_t* wm, byte event, byte* msg) {
+ save_state(wm);
+
+ switch (event) {
+ case WM_RPT_BTN:
+ {
+ /* button */
+ wiiuse_pressed_buttons(wm, msg);
+ break;
+ }
+ case WM_RPT_BTN_ACC:
+ {
+ /* button - motion */
+ wiiuse_pressed_buttons(wm, msg);
+
+ wm->accel.x = msg[2];
+ wm->accel.y = msg[3];
+ wm->accel.z = msg[4];
+
+ /* calculate the remote orientation */
+ calculate_orientation(&wm->accel_calib, &wm->accel, &wm->orient, WIIMOTE_IS_FLAG_SET(wm, WIIUSE_SMOOTHING));
+
+ /* calculate the gforces on each axis */
+ calculate_gforce(&wm->accel_calib, &wm->accel, &wm->gforce);
+
+ break;
+ }
+ case WM_RPT_READ:
+ {
+ /* data read */
+ event_data_read(wm, msg);
+
+ /* yeah buttons may be pressed, but this wasn't an "event" */
+ return;
+ }
+ case WM_RPT_CTRL_STATUS:
+ {
+ /* controller status */
+ event_status(wm, msg);
+
+ /* don't execute the event callback */
+ return;
+ }
+ case WM_RPT_BTN_EXP:
+ {
+ /* button - expansion */
+ wiiuse_pressed_buttons(wm, msg);
+ handle_expansion(wm, msg+2);
+
+ break;
+ }
+ case WM_RPT_BTN_ACC_EXP:
+ {
+ /* button - motion - expansion */
+ wiiuse_pressed_buttons(wm, msg);
+
+ wm->accel.x = msg[2];
+ wm->accel.y = msg[3];
+ wm->accel.z = msg[4];
+
+ calculate_orientation(&wm->accel_calib, &wm->accel, &wm->orient, WIIMOTE_IS_FLAG_SET(wm, WIIUSE_SMOOTHING));
+ calculate_gforce(&wm->accel_calib, &wm->accel, &wm->gforce);
+
+ handle_expansion(wm, msg+5);
+
+ break;
+ }
+ case WM_RPT_BTN_ACC_IR:
+ {
+ /* button - motion - ir */
+ wiiuse_pressed_buttons(wm, msg);
+
+ wm->accel.x = msg[2];
+ wm->accel.y = msg[3];
+ wm->accel.z = msg[4];
+
+ calculate_orientation(&wm->accel_calib, &wm->accel, &wm->orient, WIIMOTE_IS_FLAG_SET(wm, WIIUSE_SMOOTHING));
+ calculate_gforce(&wm->accel_calib, &wm->accel, &wm->gforce);
+
+ /* ir */
+ calculate_extended_ir(wm, msg+5);
+
+ break;
+ }
+ case WM_RPT_BTN_IR_EXP:
+ {
+ /* button - ir - expansion */
+ wiiuse_pressed_buttons(wm, msg);
+ handle_expansion(wm, msg+12);
+
+ /* ir */
+ calculate_basic_ir(wm, msg+2);
+
+ break;
+ }
+ case WM_RPT_BTN_ACC_IR_EXP:
+ {
+ /* button - motion - ir - expansion */
+ wiiuse_pressed_buttons(wm, msg);
+
+ wm->accel.x = msg[2];
+ wm->accel.y = msg[3];
+ wm->accel.z = msg[4];
+
+ calculate_orientation(&wm->accel_calib, &wm->accel, &wm->orient, WIIMOTE_IS_FLAG_SET(wm, WIIUSE_SMOOTHING));
+ calculate_gforce(&wm->accel_calib, &wm->accel, &wm->gforce);
+
+ handle_expansion(wm, msg+15);
+
+ /* ir */
+ calculate_basic_ir(wm, msg+5);
+
+ break;
+ }
+ case WM_RPT_WRITE:
+ {
+ /* write feedback - safe to skip */
+ break;
+ }
+ default:
+ {
+ WIIUSE_WARNING("Unknown event, can not handle it [Code 0x%x].", event);
+ return;
+ }
+ }
+
+ /* was there an event? */
+ if (state_changed(wm))
+ wm->event = WIIUSE_EVENT;
+}
+
+
+/**
+ * @brief Find what buttons are pressed.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ * @param msg The message specified in the event packet.
+ */
+void wiiuse_pressed_buttons(struct wiimote_t* wm, byte* msg) {
+ short now;
+
+ /* convert to big endian */
+ now = BIG_ENDIAN_SHORT(*(short*)msg) & WIIMOTE_BUTTON_ALL;
+
+ /* pressed now & were pressed, then held */
+ wm->btns_held = (now & wm->btns);
+
+ /* were pressed or were held & not pressed now, then released */
+ wm->btns_released = ((wm->btns | wm->btns_held) & ~now);
+
+ /* buttons pressed now */
+ wm->btns = now;
+}
+
+
+/**
+ * @brief Received a data packet from a read request.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ * @param msg The message specified in the event packet.
+ *
+ * Data from the wiimote comes in packets. If the requested
+ * data segment size is bigger than one packet can hold then
+ * several packets will be received. These packets are first
+ * reassembled into one, then the registered callback function
+ * that handles data reads is invoked.
+ */
+static void event_data_read(struct wiimote_t* wm, byte* msg) {
+ /* we must always assume the packet received is from the most recent request */
+ byte err;
+ byte len;
+ unsigned short offset;
+ struct read_req_t* req = wm->read_req;
+
+ wiiuse_pressed_buttons(wm, msg);
+ + /* find the next non-dirty request */ + while (req && req->dirty) + req = req->next; +
+ /* if we don't have a request out then we didn't ask for this packet */
+ if (!req) {
+ WIIUSE_WARNING("Received data packet when no request was made.");
+ return;
+ }
+
+ err = msg[2] & 0x0F;
+
+ if (err == 0x08)
+ WIIUSE_WARNING("Unable to read data - address does not exist.");
+ else if (err == 0x07)
+ WIIUSE_WARNING("Unable to read data - address is for write-only registers.");
+ else if (err)
+ WIIUSE_WARNING("Unable to read data - unknown error code %x.", err);
+
+ if (err) {
+ /* this request errored out, so skip it and go to the next one */
+
+ /* delete this request */
+ wm->read_req = req->next;
+ free(req);
+
+ /* if another request exists send it to the wiimote */
+ if (wm->read_req)
+ wiiuse_send_next_pending_read_request(wm);
+
+ return;
+ }
+
+ len = ((msg[2] & 0xF0) >> 4) + 1;
+ offset = BIG_ENDIAN_SHORT(*(unsigned short*)(msg + 3));
+ req->addr = (req->addr & 0xFFFF);
+
+ req->wait -= len;
+ if (req->wait >= req->size)
+ /* this should never happen */
+ req->wait = 0;
+
+ WIIUSE_DEBUG("Received read packet:");
+ WIIUSE_DEBUG(" Packet read offset: %i bytes", offset);
+ WIIUSE_DEBUG(" Request read offset: %i bytes", req->addr);
+ WIIUSE_DEBUG(" Read offset into buf: %i bytes", offset - req->addr);
+ WIIUSE_DEBUG(" Read data size: %i bytes", len);
+ WIIUSE_DEBUG(" Still need: %i bytes", req->wait);
+
+ /* reconstruct this part of the data */
+ memcpy((req->buf + offset - req->addr), (msg + 5), len);
+
+ #ifdef WITH_WIIUSE_DEBUG
+ {
+ int i = 0;
+ printf("Read: ");
+ for (; i < req->size - req->wait; ++i)
+ printf("%x ", req->buf[i]);
+ printf("\n");
+ }
+ #endif
+
+ /* if all data has been received, execute the read event callback or generate event */
+ if (!req->wait) { + if (req->cb) { + /* this was a callback, so invoke it now */
+ req->cb(wm, req->buf, req->size);
+
+ /* delete this request */
+ wm->read_req = req->next;
+ free(req); + } else { + /* + * This should generate an event. + * We need to leave the event in the array so the client + * can access it still. We'll flag is as being 'dirty' + * and give the client one cycle to use it. Next event + * we will remove it from the list. + */ + wm->event = WIIUSE_READ_DATA; + req->dirty = 1; + }
+
+ /* if another request exists send it to the wiimote */
+ if (wm->read_req)
+ wiiuse_send_next_pending_read_request(wm);
+ }
+}
+
+
+/**
+ * @brief Read the controller status.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ * @param msg The message specified in the event packet.
+ *
+ * Read the controller status and execute the registered status callback.
+ */
+static void event_status(struct wiimote_t* wm, byte* msg) {
+ int led[4] = {0};
+ int attachment = 0;
+ int ir = 0;
+ int exp_changed = 0;
+
+ /*
+ * An event occured.
+ * This event can be overwritten by a more specific
+ * event type during a handshake or expansion removal.
+ */
+ wm->event = WIIUSE_STATUS;
+
+ wiiuse_pressed_buttons(wm, msg);
+
+ /* find what LEDs are lit */
+ if (msg[2] & WM_CTRL_STATUS_BYTE1_LED_1) led[0] = 1;
+ if (msg[2] & WM_CTRL_STATUS_BYTE1_LED_2) led[1] = 1;
+ if (msg[2] & WM_CTRL_STATUS_BYTE1_LED_3) led[2] = 1;
+ if (msg[2] & WM_CTRL_STATUS_BYTE1_LED_4) led[3] = 1;
+
+ /* is an attachment connected to the expansion port? */
+ if ((msg[2] & WM_CTRL_STATUS_BYTE1_ATTACHMENT) == WM_CTRL_STATUS_BYTE1_ATTACHMENT)
+ attachment = 1;
+
+ /* is the speaker enabled? */
+ if ((msg[2] & WM_CTRL_STATUS_BYTE1_SPEAKER_ENABLED) == WM_CTRL_STATUS_BYTE1_SPEAKER_ENABLED)
+ WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_SPEAKER);
+
+ /* is IR sensing enabled? */
+ if ((msg[2] & WM_CTRL_STATUS_BYTE1_IR_ENABLED) == WM_CTRL_STATUS_BYTE1_IR_ENABLED)
+ ir = 1;
+
+ /* find the battery level and normalize between 0 and 1 */
+ wm->battery_level = (msg[5] / (float)WM_MAX_BATTERY_CODE);
+
+ /* expansion port */
+ if (attachment && !WIIMOTE_IS_SET(wm, WIIMOTE_STATE_EXP)) {
+ /* send the initialization code for the attachment */
+ handshake_expansion(wm, NULL, 0);
+ exp_changed = 1;
+ } else if (!attachment && WIIMOTE_IS_SET(wm, WIIMOTE_STATE_EXP)) {
+ /* attachment removed */
+ disable_expansion(wm);
+ exp_changed = 1;
+ }
+
+ #ifdef WIN32
+ if (!attachment) {
+ WIIUSE_DEBUG("Setting timeout to normal %i ms.", wm->normal_timeout);
+ wm->timeout = wm->normal_timeout;
+ }
+ #endif
+
+ /*
+ * From now on the remote will only send status packets.
+ * We need to send a WIIMOTE_CMD_REPORT_TYPE packet to
+ * reenable other incoming reports.
+ */
+ if (exp_changed && WIIMOTE_IS_SET(wm, WIIMOTE_STATE_IR)) {
+ /*
+ * Since the expansion status changed IR needs to
+ * be reset for the new IR report mode.
+ */
+ WIIMOTE_DISABLE_STATE(wm, WIIMOTE_STATE_IR);
+ wiiuse_set_ir(wm, 1);
+ } else
+ wiiuse_set_report_type(wm);
+}
+
+
+/**
+ * @brief Handle data from the expansion.
+ *
+ * @param wm A pointer to a wiimote_t structure.
+ * @param msg The message specified in the event packet for the expansion.
+ */
+static void handle_expansion(struct wiimote_t* wm, byte* msg) {
+ switch (wm->exp.type) {
+ case EXP_NUNCHUK:
+ nunchuk_event(&wm->exp.nunchuk, msg);
+ break;
+ case EXP_CLASSIC:
+ classic_ctrl_event(&wm->exp.classic, msg);
+ break;
+ case EXP_GUITAR_HERO_3:
+ guitar_hero_3_event(&wm->exp.gh3, msg);
+ break;
+ default:
+ break;
+ }
+}
+
+
+/**
+ * @brief Handle the handshake data from the expansion device.
+ *
+ * @param wm A pointer to a wiimote_t structure.
+ * @param data The data read in from the device.
+ * @param len The length of the data block, in bytes.
+ *
+ * Tries to determine what kind of expansion was attached
+ * and invoke the correct handshake function.
+ *
+ * If the data is NULL then this function will try to start
+ * a handshake with the expansion.
+ */
+void handshake_expansion(struct wiimote_t* wm, byte* data, unsigned short len) {
+ int id;
+
+ if (!data) {
+ byte* handshake_buf;
+ byte buf = 0x00;
+
+ if (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_EXP))
+ disable_expansion(wm);
+
+ /* increase the timeout until the handshake completes */
+ #ifdef WIN32
+ WIIUSE_DEBUG("Setting timeout to expansion %i ms.", wm->exp_timeout);
+ wm->timeout = wm->exp_timeout;
+ #endif
+
+ wiiuse_write_data(wm, WM_EXP_MEM_ENABLE, &buf, 1);
+
+ /* get the calibration data */
+ handshake_buf = malloc(EXP_HANDSHAKE_LEN * sizeof(byte));
+ wiiuse_read_data_cb(wm, handshake_expansion, handshake_buf, WM_EXP_MEM_CALIBR, EXP_HANDSHAKE_LEN);
+
+ /* tell the wiimote to send expansion data */
+ WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_EXP);
+
+ return;
+ }
+
+ id = BIG_ENDIAN_LONG(*(int*)(data + 220));
+
+ /* call the corresponding handshake function for this expansion */
+ switch (id) {
+ case EXP_ID_CODE_NUNCHUK:
+ {
+ if (nunchuk_handshake(wm, &wm->exp.nunchuk, data, len))
+ wm->event = WIIUSE_NUNCHUK_INSERTED;
+ break;
+ }
+ case EXP_ID_CODE_CLASSIC_CONTROLLER:
+ { + if (classic_ctrl_handshake(wm, &wm->exp.classic, data, len))
+ wm->event = WIIUSE_CLASSIC_CTRL_INSERTED; + break;
+ }
+ case EXP_ID_CODE_GUITAR:
+ {
+ if (guitar_hero_3_handshake(wm, &wm->exp.gh3, data, len))
+ wm->event = WIIUSE_GUITAR_HERO_3_CTRL_INSERTED;
+ break;
+ }
+ default:
+ {
+ WIIUSE_WARNING("Unknown expansion type. Code: 0x%x", id);
+ break;
+ }
+ }
+
+ free(data);
+}
+
+
+
+/**
+ * @brief Disable the expansion device if it was enabled.
+ *
+ * @param wm A pointer to a wiimote_t structure.
+ * @param data The data read in from the device.
+ * @param len The length of the data block, in bytes.
+ *
+ * If the data is NULL then this function will try to start
+ * a handshake with the expansion.
+ */
+void disable_expansion(struct wiimote_t* wm) {
+ if (!WIIMOTE_IS_SET(wm, WIIMOTE_STATE_EXP))
+ return;
+
+ /* tell the assoicated module the expansion was removed */
+ switch (wm->exp.type) {
+ case EXP_NUNCHUK:
+ nunchuk_disconnected(&wm->exp.nunchuk);
+ wm->event = WIIUSE_NUNCHUK_REMOVED;
+ break;
+ case EXP_CLASSIC:
+ classic_ctrl_disconnected(&wm->exp.classic);
+ wm->event = WIIUSE_CLASSIC_CTRL_REMOVED;
+ break;
+ case EXP_GUITAR_HERO_3:
+ guitar_hero_3_disconnected(&wm->exp.gh3);
+ wm->event = WIIUSE_GUITAR_HERO_3_CTRL_REMOVED;
+ break;
+ default:
+ break;
+ }
+
+ WIIMOTE_DISABLE_STATE(wm, WIIMOTE_STATE_EXP);
+ wm->exp.type = EXP_NONE;
+}
+
+
+/**
+ * @brief Save important state data.
+ * @param wm A pointer to a wiimote_t structure.
+ */
+static void save_state(struct wiimote_t* wm) {
+ /* wiimote */
+ wm->lstate.btns = wm->btns;
+ wm->lstate.accel = wm->accel;
+
+ /* ir */
+ if (WIIUSE_USING_IR(wm)) {
+ wm->lstate.ir_ax = wm->ir.ax;
+ wm->lstate.ir_ay = wm->ir.ay;
+ wm->lstate.ir_distance = wm->ir.distance;
+ }
+
+ /* expansion */
+ switch (wm->exp.type) {
+ case EXP_NUNCHUK:
+ wm->lstate.exp_ljs_ang = wm->exp.nunchuk.js.ang;
+ wm->lstate.exp_ljs_mag = wm->exp.nunchuk.js.mag;
+ wm->lstate.exp_btns = wm->exp.nunchuk.btns;
+ wm->lstate.exp_accel = wm->exp.nunchuk.accel;
+ break;
+
+ case EXP_CLASSIC:
+ wm->lstate.exp_ljs_ang = wm->exp.classic.ljs.ang;
+ wm->lstate.exp_ljs_mag = wm->exp.classic.ljs.mag;
+ wm->lstate.exp_rjs_ang = wm->exp.classic.rjs.ang;
+ wm->lstate.exp_rjs_mag = wm->exp.classic.rjs.mag;
+ wm->lstate.exp_r_shoulder = wm->exp.classic.r_shoulder;
+ wm->lstate.exp_l_shoulder = wm->exp.classic.l_shoulder;
+ wm->lstate.exp_btns = wm->exp.classic.btns;
+ break;
+
+ case EXP_GUITAR_HERO_3:
+ wm->lstate.exp_ljs_ang = wm->exp.gh3.js.ang;
+ wm->lstate.exp_ljs_mag = wm->exp.gh3.js.mag;
+ wm->lstate.exp_r_shoulder = wm->exp.gh3.whammy_bar;
+ wm->lstate.exp_btns = wm->exp.gh3.btns;
+ break;
+
+ case EXP_NONE:
+ break;
+ }
+}
+
+
+/**
+ * @brief Determine if the current state differs significantly from the previous.
+ * @param wm A pointer to a wiimote_t structure.
+ * @return 1 if a significant change occured, 0 if not.
+ */
+static int state_changed(struct wiimote_t* wm) {
+ #define STATE_CHANGED(a, b) if (a != b) return 1
+
+ #define CROSS_THRESH(last, now, thresh) \
+ do { \
+ if (WIIMOTE_IS_FLAG_SET(wm, WIIUSE_ORIENT_THRESH)) { \
+ if ((diff_f(last.roll, now.roll) >= thresh) || \
+ (diff_f(last.pitch, now.pitch) >= thresh) || \
+ (diff_f(last.yaw, now.yaw) >= thresh)) \
+ { \
+ last = now; \
+ return 1; \
+ } \
+ } else { \
+ if (last.roll != now.roll) return 1; \
+ if (last.pitch != now.pitch) return 1; \
+ if (last.yaw != now.yaw) return 1; \
+ } \
+ } while (0)
+
+ #define CROSS_THRESH_XYZ(last, now, thresh) \
+ do { \
+ if (WIIMOTE_IS_FLAG_SET(wm, WIIUSE_ORIENT_THRESH)) { \
+ if ((diff_f(last.x, now.x) >= thresh) || \
+ (diff_f(last.y, now.y) >= thresh) || \
+ (diff_f(last.z, now.z) >= thresh)) \
+ { \
+ last = now; \
+ return 1; \
+ } \
+ } else { \
+ if (last.x != now.x) return 1; \
+ if (last.y != now.y) return 1; \
+ if (last.z != now.z) return 1; \
+ } \
+ } while (0)
+
+ /* ir */
+ if (WIIUSE_USING_IR(wm)) {
+ STATE_CHANGED(wm->lstate.ir_ax, wm->ir.ax);
+ STATE_CHANGED(wm->lstate.ir_ay, wm->ir.ay);
+ STATE_CHANGED(wm->lstate.ir_distance, wm->ir.distance);
+ }
+
+ /* accelerometer */
+ if (WIIUSE_USING_ACC(wm)) {
+ /* raw accelerometer */
+ CROSS_THRESH_XYZ(wm->lstate.accel, wm->accel, wm->accel_threshold);
+
+ /* orientation */
+ CROSS_THRESH(wm->lstate.orient, wm->orient, wm->orient_threshold);
+ }
+
+ /* expansion */
+ switch (wm->exp.type) {
+ case EXP_NUNCHUK:
+ {
+ STATE_CHANGED(wm->lstate.exp_ljs_ang, wm->exp.nunchuk.js.ang);
+ STATE_CHANGED(wm->lstate.exp_ljs_mag, wm->exp.nunchuk.js.mag);
+ STATE_CHANGED(wm->lstate.exp_btns, wm->exp.nunchuk.btns);
+
+ CROSS_THRESH(wm->lstate.exp_orient, wm->exp.nunchuk.orient, wm->exp.nunchuk.orient_threshold);
+ CROSS_THRESH_XYZ(wm->lstate.exp_accel, wm->exp.nunchuk.accel, wm->exp.nunchuk.accel_threshold);
+ break;
+ }
+ case EXP_CLASSIC:
+ {
+ STATE_CHANGED(wm->lstate.exp_ljs_ang, wm->exp.classic.ljs.ang);
+ STATE_CHANGED(wm->lstate.exp_ljs_mag, wm->exp.classic.ljs.mag);
+ STATE_CHANGED(wm->lstate.exp_rjs_ang, wm->exp.classic.rjs.ang);
+ STATE_CHANGED(wm->lstate.exp_rjs_mag, wm->exp.classic.rjs.mag);
+ STATE_CHANGED(wm->lstate.exp_r_shoulder, wm->exp.classic.r_shoulder);
+ STATE_CHANGED(wm->lstate.exp_l_shoulder, wm->exp.classic.l_shoulder);
+ STATE_CHANGED(wm->lstate.exp_btns, wm->exp.classic.btns);
+ break;
+ }
+ case EXP_GUITAR_HERO_3:
+ {
+ STATE_CHANGED(wm->lstate.exp_ljs_ang, wm->exp.gh3.js.ang);
+ STATE_CHANGED(wm->lstate.exp_ljs_mag, wm->exp.gh3.js.mag);
+ STATE_CHANGED(wm->lstate.exp_r_shoulder, wm->exp.gh3.whammy_bar);
+ STATE_CHANGED(wm->lstate.exp_btns, wm->exp.gh3.btns);
+ break;
+ }
+ case EXP_NONE:
+ {
+ break;
+ }
+ }
+
+ STATE_CHANGED(wm->lstate.btns, wm->btns);
+
+ return 0;
+}
diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/events.h b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/events.h new file mode 100644 index 0000000000..5e9b955c55 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/events.h @@ -0,0 +1,54 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + +/** + * @file + * @brief Handles wiimote events. + * + * The file includes functions that handle the events + * that are sent from the wiimote to us. + */ + +#ifndef EVENTS_H_INCLUDED +#define EVENTS_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +void wiiuse_pressed_buttons(struct wiimote_t* wm, byte* msg); + +void handshake_expansion(struct wiimote_t* wm, byte* data, unsigned short len); +void disable_expansion(struct wiimote_t* wm);
+ +#ifdef __cplusplus +} +#endif + + +#endif // EVENTS_H_INCLUDED diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/guitar_hero_3.c b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/guitar_hero_3.c new file mode 100644 index 0000000000..5837dc016f --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/guitar_hero_3.c @@ -0,0 +1,172 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + +/** + * @file + * @brief Guitar Hero 3 expansion device. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> + +#ifdef WIN32 + #include <Winsock2.h> +#endif + +#include "definitions.h" +#include "wiiuse_internal.h" +#include "dynamics.h" +#include "events.h" +#include "guitar_hero_3.h" + +static void guitar_hero_3_pressed_buttons(struct guitar_hero_3_t* gh3, short now); + +/** + * @brief Handle the handshake data from the guitar. + * + * @param cc A pointer to a classic_ctrl_t structure. + * @param data The data read in from the device. + * @param len The length of the data block, in bytes. + * + * @return Returns 1 if handshake was successful, 0 if not. + */ +int guitar_hero_3_handshake(struct wiimote_t* wm, struct guitar_hero_3_t* gh3, byte* data, unsigned short len) { + int i;
+ int offset = 0; + + /* + * The good fellows that made the Guitar Hero 3 controller + * failed to factory calibrate the devices. There is no + * calibration data on the device. + */ + + gh3->btns = 0; + gh3->btns_held = 0; + gh3->btns_released = 0; + gh3->whammy_bar = 0.0f; + + /* decrypt data */ + for (i = 0; i < len; ++i) + data[i] = (data[i] ^ 0x17) + 0x17; +
+ if (data[offset] == 0xFF) {
+ /*
+ * Sometimes the data returned here is not correct.
+ * This might happen because the wiimote is lagging
+ * behind our initialization sequence.
+ * To fix this just request the handshake again.
+ *
+ * Other times it's just the first 16 bytes are 0xFF,
+ * but since the next 16 bytes are the same, just use
+ * those.
+ */
+ if (data[offset + 16] == 0xFF) {
+ /* get the calibration data */
+ byte* handshake_buf = malloc(EXP_HANDSHAKE_LEN * sizeof(byte));
+
+ WIIUSE_DEBUG("Guitar Hero 3 handshake appears invalid, trying again.");
+ wiiuse_read_data_cb(wm, handshake_expansion, handshake_buf, WM_EXP_MEM_CALIBR, EXP_HANDSHAKE_LEN);
+
+ return 0;
+ } else
+ offset += 16;
+ }
+ + /* joystick stuff */ + gh3->js.max.x = GUITAR_HERO_3_JS_MAX_X; + gh3->js.min.x = GUITAR_HERO_3_JS_MIN_X; + gh3->js.center.x = GUITAR_HERO_3_JS_CENTER_X; + gh3->js.max.y = GUITAR_HERO_3_JS_MAX_Y; + gh3->js.min.y = GUITAR_HERO_3_JS_MIN_Y; + gh3->js.center.y = GUITAR_HERO_3_JS_CENTER_Y; + + /* handshake done */ + wm->exp.type = EXP_GUITAR_HERO_3; + + #ifdef WIN32 + wm->timeout = WIIMOTE_DEFAULT_TIMEOUT; + #endif + + return 1; +} + + +/** + * @brief The guitar disconnected. + * + * @param cc A pointer to a classic_ctrl_t structure. + */ +void guitar_hero_3_disconnected(struct guitar_hero_3_t* gh3) { + memset(gh3, 0, sizeof(struct guitar_hero_3_t)); +} + + + +/** + * @brief Handle guitar event. + * + * @param cc A pointer to a classic_ctrl_t structure. + * @param msg The message specified in the event packet. + */ +void guitar_hero_3_event(struct guitar_hero_3_t* gh3, byte* msg) { + int i; + + /* decrypt data */ + for (i = 0; i < 6; ++i) + msg[i] = (msg[i] ^ 0x17) + 0x17; + + guitar_hero_3_pressed_buttons(gh3, BIG_ENDIAN_SHORT(*(short*)(msg + 4))); + + /* whammy bar */ + gh3->whammy_bar = (msg[3] - GUITAR_HERO_3_WHAMMY_BAR_MIN) / (float)(GUITAR_HERO_3_WHAMMY_BAR_MAX - GUITAR_HERO_3_WHAMMY_BAR_MIN); + + /* joy stick */ + calc_joystick_state(&gh3->js, msg[0], msg[1]); +} + + +/** + * @brief Find what buttons are pressed. + * + * @param cc A pointer to a classic_ctrl_t structure. + * @param msg The message byte specified in the event packet. + */ +static void guitar_hero_3_pressed_buttons(struct guitar_hero_3_t* gh3, short now) { + /* message is inverted (0 is active, 1 is inactive) */ + now = ~now & GUITAR_HERO_3_BUTTON_ALL; + + /* pressed now & were pressed, then held */ + gh3->btns_held = (now & gh3->btns); + + /* were pressed or were held & not pressed now, then released */ + gh3->btns_released = ((gh3->btns | gh3->btns_held) & ~now); + + /* buttons pressed now */ + gh3->btns = now; +} diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/guitar_hero_3.h b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/guitar_hero_3.h new file mode 100644 index 0000000000..024d603177 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/guitar_hero_3.h @@ -0,0 +1,62 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + +/** + * @file + * @brief Guitar Hero 3 expansion device. + */ + +#ifndef GUITAR_HERO_3_H_INCLUDED +#define GUITAR_HERO_3_H_INCLUDED + +#include "wiiuse_internal.h" + +#define GUITAR_HERO_3_JS_MIN_X 0xC5 +#define GUITAR_HERO_3_JS_MAX_X 0xFC +#define GUITAR_HERO_3_JS_CENTER_X 0xE0 +#define GUITAR_HERO_3_JS_MIN_Y 0xC5 +#define GUITAR_HERO_3_JS_MAX_Y 0xFA +#define GUITAR_HERO_3_JS_CENTER_Y 0xE0 +#define GUITAR_HERO_3_WHAMMY_BAR_MIN 0xEF +#define GUITAR_HERO_3_WHAMMY_BAR_MAX 0xFA + +#ifdef __cplusplus +extern "C" { +#endif + +int guitar_hero_3_handshake(struct wiimote_t* wm, struct guitar_hero_3_t* gh3, byte* data, unsigned short len); + +void guitar_hero_3_disconnected(struct guitar_hero_3_t* gh3); + +void guitar_hero_3_event(struct guitar_hero_3_t* gh3, byte* msg); + +#ifdef __cplusplus +} +#endif + +#endif // GUITAR_HERO_3_H_INCLUDED diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/io.c b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/io.c new file mode 100644 index 0000000000..ae420b9ef3 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/io.c @@ -0,0 +1,119 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + +/** + * @file + * @brief Handles device I/O (non-OS specific). + */ + +#include <stdio.h> +#include <stdlib.h> + +#include "definitions.h" +#include "wiiuse_internal.h" +#include "io.h" + + + /** + * @brief Get initialization data from the wiimote. + * + * @param wm Pointer to a wiimote_t structure. + * @param data unused + * @param len unused + * + * When first called for a wiimote_t structure, a request + * is sent to the wiimote for initialization information. + * This includes factory set accelerometer data. + * The handshake will be concluded when the wiimote responds + * with this data. + */ +void wiiuse_handshake(struct wiimote_t* wm, byte* data, unsigned short len) { + if (!wm) return; + + switch (wm->handshake_state) { + case 0: + { + /* send request to wiimote for accelerometer calibration */ + byte* buf;
+
+ WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_HANDSHAKE); + wiiuse_set_leds(wm, WIIMOTE_LED_NONE); + + buf = (byte*)malloc(sizeof(byte) * 8); + wiiuse_read_data_cb(wm, wiiuse_handshake, buf, WM_MEM_OFFSET_CALIBRATION, 7); + wm->handshake_state++; + + wiiuse_set_leds(wm, WIIMOTE_LED_NONE);
+
+ break; + } + case 1: + { + struct read_req_t* req = wm->read_req; + struct accel_t* accel = &wm->accel_calib; + + /* received read data */ + accel->cal_zero.x = req->buf[0]; + accel->cal_zero.y = req->buf[1]; + accel->cal_zero.z = req->buf[2]; + + accel->cal_g.x = req->buf[4] - accel->cal_zero.x; + accel->cal_g.y = req->buf[5] - accel->cal_zero.y; + accel->cal_g.z = req->buf[6] - accel->cal_zero.z; + + /* done with the buffer */ + free(req->buf); + + /* handshake is done */ + WIIUSE_DEBUG("Handshake finished. Calibration: Idle: X=%x Y=%x Z=%x\t+1g: X=%x Y=%x Z=%x", + accel->cal_zero.x, accel->cal_zero.y, accel->cal_zero.z, + accel->cal_g.x, accel->cal_g.y, accel->cal_g.z); + +
+ /* request the status of the wiimote to see if there is an expansion */
+ wiiuse_status(wm);
+
+ WIIMOTE_DISABLE_STATE(wm, WIIMOTE_STATE_HANDSHAKE); + WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_HANDSHAKE_COMPLETE); + wm->handshake_state++;
+
+ /* now enable IR if it was set before the handshake completed */
+ if (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_IR)) {
+ WIIUSE_DEBUG("Handshake finished, enabling IR."); + WIIMOTE_DISABLE_STATE(wm, WIIMOTE_STATE_IR); + wiiuse_set_ir(wm, 1); + }
+ + break; + }
+ default: + { + break; + } + } +} diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/io.h b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/io.h new file mode 100644 index 0000000000..7a683e4ee5 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/io.h @@ -0,0 +1,56 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + +/** + * @file + * @brief Handles device I/O. + */ + +#ifndef CONNECT_H_INCLUDED +#define CONNECT_H_INCLUDED + +#ifndef WIN32 + #include <bluetooth/bluetooth.h> +#endif + +#include "wiiuse_internal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void wiiuse_handshake(struct wiimote_t* wm, byte* data, unsigned short len); + +int wiiuse_io_read(struct wiimote_t* wm); +int wiiuse_io_write(struct wiimote_t* wm, byte* buf, int len); + +#ifdef __cplusplus +} +#endif + +#endif // CONNECT_H_INCLUDED diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/io_nix.c b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/io_nix.c new file mode 100644 index 0000000000..ec4e0e1780 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/io_nix.c @@ -0,0 +1,270 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + +/** + * @file + * @brief Handles device I/O for *nix. + */ + +#ifndef WIN32 + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/l2cap.h> + +#include "definitions.h" +#include "wiiuse_internal.h" +#include "io.h" + +static int wiiuse_connect_single(struct wiimote_t* wm, char* address); + +/** + * @brief Find a wiimote or wiimotes. + * + * @param wm An array of wiimote_t structures. + * @param max_wiimotes The number of wiimote structures in \a wm. + * @param timeout The number of seconds before the search times out. + * + * @return The number of wiimotes found. + * + * @see wiimote_connect() + * + * This function will only look for wiimote devices. \n + * When a device is found the address in the structures will be set. \n + * You can then call wiimote_connect() to connect to the found \n + * devices. + */ +int wiiuse_find(struct wiimote_t** wm, int max_wiimotes, int timeout) { + int device_id; + int device_sock; + int found_devices; + int found_wiimotes; + + /* reset all wiimote bluetooth device addresses */ + for (found_wiimotes = 0; found_wiimotes < max_wiimotes; ++found_wiimotes) + wm[found_wiimotes]->bdaddr = *BDADDR_ANY; + found_wiimotes = 0; + + /* get the id of the first bluetooth device. */ + device_id = hci_get_route(NULL); + if (device_id < 0) { + perror("hci_get_route"); + return 0; + } + + /* create a socket to the device */ + device_sock = hci_open_dev(device_id); + if (device_sock < 0) { + perror("hci_open_dev"); + return 0; + } + + inquiry_info scan_info_arr[128]; + inquiry_info* scan_info = scan_info_arr; + memset(&scan_info_arr, 0, sizeof(scan_info_arr)); + + /* scan for bluetooth devices for 'timeout' seconds */ + found_devices = hci_inquiry(device_id, timeout, 128, NULL, &scan_info, IREQ_CACHE_FLUSH); + if (found_devices < 0) { + perror("hci_inquiry"); + return 0; + } + + WIIUSE_INFO("Found %i bluetooth device(s).", found_devices); + + int i = 0; + + /* display discovered devices */ + for (; (i < found_devices) && (found_wiimotes < max_wiimotes); ++i) { + if ((scan_info[i].dev_class[0] == WM_DEV_CLASS_0) && + (scan_info[i].dev_class[1] == WM_DEV_CLASS_1) && + (scan_info[i].dev_class[2] == WM_DEV_CLASS_2)) + { + /* found a device */ + ba2str(&scan_info[i].bdaddr, wm[found_wiimotes]->bdaddr_str); + + WIIUSE_INFO("Found wiimote (%s) [id %i].", wm[found_wiimotes]->bdaddr_str, wm[found_wiimotes]->unid); + + wm[found_wiimotes]->bdaddr = scan_info[i].bdaddr; + WIIMOTE_ENABLE_STATE(wm[found_wiimotes], WIIMOTE_STATE_DEV_FOUND); + ++found_wiimotes; + } + } + + close(device_sock); + return found_wiimotes; +} + + +/** + * @brief Connect to a wiimote or wiimotes once an address is known. + * + * @param wm An array of wiimote_t structures. + * @param wiimotes The number of wiimote structures in \a wm. + * + * @return The number of wiimotes that successfully connected. + * + * @see wiiuse_find() + * @see wiiuse_connect_single() + * @see wiiuse_disconnect() + * + * Connect to a number of wiimotes when the address is already set + * in the wiimote_t structures. These addresses are normally set + * by the wiiuse_find() function, but can also be set manually. + */ +int wiiuse_connect(struct wiimote_t** wm, int wiimotes) { + int connected = 0; + int i = 0; + + for (; i < wiimotes; ++i) { + if (!WIIMOTE_IS_SET(wm[i], WIIMOTE_STATE_DEV_FOUND)) + /* if the device address is not set, skip it */ + continue; + + if (wiiuse_connect_single(wm[i], NULL)) + ++connected; + } + + return connected; +} + + +/** + * @brief Connect to a wiimote with a known address. + * + * @param wm Pointer to a wiimote_t structure. + * @param address The address of the device to connect to. + * If NULL, use the address in the struct set by wiiuse_find(). + * + * @return 1 on success, 0 on failure + */ +static int wiiuse_connect_single(struct wiimote_t* wm, char* address) { + struct sockaddr_l2 addr; + + if (!wm || WIIMOTE_IS_CONNECTED(wm)) + return 0; + + addr.l2_family = AF_BLUETOOTH; + + if (address) + /* use provided address */ + str2ba(address, &addr.l2_bdaddr); + else + /* use address of device discovered */ + addr.l2_bdaddr = wm->bdaddr; + + /* + * OUTPUT CHANNEL + */ + wm->out_sock = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (wm->out_sock == -1) + return 0; + + addr.l2_psm = htobs(WM_OUTPUT_CHANNEL); + + /* connect to wiimote */ + if (connect(wm->out_sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + perror("connect() output sock"); + return 0; + } + + /* + * INPUT CHANNEL + */ + wm->in_sock = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (wm->in_sock == -1) { + close(wm->out_sock); + wm->out_sock = -1; + return 0; + } + + addr.l2_psm = htobs(WM_INPUT_CHANNEL); + + /* connect to wiimote */ + if (connect(wm->in_sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + perror("connect() interrupt sock"); + close(wm->out_sock); + wm->out_sock = -1; + return 0; + } + + WIIUSE_INFO("Connected to wiimote [id %i].", wm->unid); + + /* do the handshake */ + WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_CONNECTED); + wiiuse_handshake(wm, NULL, 0); + + wiiuse_set_report_type(wm); + + return 1; +} + + +/** + * @brief Disconnect a wiimote. + * + * @param wm Pointer to a wiimote_t structure. + * + * @see wiiuse_connect() + * + * Note that this will not free the wiimote structure. + */ +void wiiuse_disconnect(struct wiimote_t* wm) { + if (!wm || WIIMOTE_IS_CONNECTED(wm)) + return; + + close(wm->out_sock); + close(wm->in_sock); + + wm->out_sock = -1; + wm->in_sock = -1; + wm->event = WIIUSE_NONE; + + WIIMOTE_DISABLE_STATE(wm, WIIMOTE_STATE_CONNECTED); + WIIMOTE_DISABLE_STATE(wm, WIIMOTE_STATE_HANDSHAKE); +} + + +int wiiuse_io_read(struct wiimote_t* wm) { + /* not used */ + return 0; +} + + +int wiiuse_io_write(struct wiimote_t* wm, byte* buf, int len) { + return write(wm->out_sock, buf, len); +} + + + +#endif /* ifndef WIN32 */ diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/io_win.c b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/io_win.c new file mode 100644 index 0000000000..17fac360d3 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/io_win.c @@ -0,0 +1,247 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + +/** + * @file + * @brief Handles device I/O for Windows. + */ + +#ifdef WIN32 +
+#include <stdio.h> +#include <stdlib.h> + +#include <windows.h> +#include <hidsdi.h> +#include <setupapi.h> +
+#include "definitions.h" +#include "wiiuse_internal.h" +#include "io.h" + + +int wiiuse_find(struct wiimote_t** wm, int max_wiimotes, int timeout) { + GUID device_id; + HANDLE dev; + HDEVINFO device_info; + int i, index; + DWORD len; + SP_DEVICE_INTERFACE_DATA device_data; + PSP_DEVICE_INTERFACE_DETAIL_DATA detail_data = NULL; + HIDD_ATTRIBUTES attr; + int found = 0; + + (void) timeout; // unused + + device_data.cbSize = sizeof(device_data); + index = 0; + + /* get the device id */ + HidD_GetHidGuid(&device_id); + + /* get all hid devices connected */ + device_info = SetupDiGetClassDevs(&device_id, NULL, NULL, (DIGCF_DEVICEINTERFACE | DIGCF_PRESENT)); + + for (;; ++index) { + + if (detail_data) { + free(detail_data); + detail_data = NULL; + } + + /* query the next hid device info */ + if (!SetupDiEnumDeviceInterfaces(device_info, NULL, &device_id, index, &device_data)) + break; + + /* get the size of the data block required */ + i = SetupDiGetDeviceInterfaceDetail(device_info, &device_data, NULL, 0, &len, NULL); + detail_data = malloc(len); + detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + /* query the data for this device */ + if (!SetupDiGetDeviceInterfaceDetail(device_info, &device_data, detail_data, len, NULL, NULL)) + continue; + + /* open the device */ + dev = CreateFile(detail_data->DevicePath, + (GENERIC_READ | GENERIC_WRITE), + (FILE_SHARE_READ | FILE_SHARE_WRITE), + NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + if (dev == INVALID_HANDLE_VALUE) + continue; + + /* get device attributes */ + attr.Size = sizeof(attr); + i = HidD_GetAttributes(dev, &attr); + + if ((attr.VendorID == WM_VENDOR_ID) && (attr.ProductID == WM_PRODUCT_ID)) { + /* this is a wiimote */ + wm[found]->dev_handle = dev; + + wm[found]->hid_overlap.hEvent = CreateEvent(NULL, 1, 1, ""); + wm[found]->hid_overlap.Offset = 0; + wm[found]->hid_overlap.OffsetHigh = 0; + + WIIMOTE_ENABLE_STATE(wm[found], WIIMOTE_STATE_DEV_FOUND); + WIIMOTE_ENABLE_STATE(wm[found], WIIMOTE_STATE_CONNECTED); + + /* try to set the output report to see if the device is actually connected */ + if (!wiiuse_set_report_type(wm[found])) { + WIIMOTE_DISABLE_STATE(wm[found], WIIMOTE_STATE_CONNECTED); + continue; + } + + /* do the handshake */ + wiiuse_handshake(wm[found], NULL, 0); + + WIIUSE_INFO("Connected to wiimote [id %i].", wm[found]->unid); + + ++found; + if (found >= max_wiimotes) + break; + } else { + /* not a wiimote */ + CloseHandle(dev); + } + } + + if (detail_data) + free(detail_data); + + SetupDiDestroyDeviceInfoList(device_info); + + return found; +} + + +int wiiuse_connect(struct wiimote_t** wm, int wiimotes) { + int connected = 0; + int i = 0; + + for (; i < wiimotes; ++i) { + if (WIIMOTE_IS_SET(wm[i], WIIMOTE_STATE_CONNECTED)) + ++connected; + } + + return connected; +} + + +void wiiuse_disconnect(struct wiimote_t* wm) { + if (!wm || WIIMOTE_IS_CONNECTED(wm)) + return; + + CloseHandle(wm->dev_handle); + wm->dev_handle = 0; + + ResetEvent(&wm->hid_overlap); + + wm->event = WIIUSE_NONE; + + WIIMOTE_DISABLE_STATE(wm, WIIMOTE_STATE_CONNECTED); + WIIMOTE_DISABLE_STATE(wm, WIIMOTE_STATE_HANDSHAKE); +} + + +int wiiuse_io_read(struct wiimote_t* wm) { + DWORD b, r; + + if (!wm || !WIIMOTE_IS_CONNECTED(wm)) + return 0; + + if (!ReadFile(wm->dev_handle, wm->event_buf, sizeof(wm->event_buf), &b, &wm->hid_overlap)) { + /* partial read */ + b = GetLastError();
+
+ if ((b == ERROR_HANDLE_EOF) || (b == ERROR_DEVICE_NOT_CONNECTED)) { + /* remote disconnect */ + wiiuse_disconnected(wm); + return 0; + } + + r = WaitForSingleObject(wm->hid_overlap.hEvent, wm->timeout); + if (r == WAIT_TIMEOUT) { + /* timeout - cancel and continue */
+
+ if (*wm->event_buf)
+ WIIUSE_WARNING("Packet ignored. This may indicate a problem (timeout is %i ms).", wm->timeout);
+
+ CancelIo(wm->dev_handle); + ResetEvent(wm->hid_overlap.hEvent); + return 0; + } else if (r == WAIT_FAILED) { + WIIUSE_WARNING("A wait error occured on reading from wiimote %i.", wm->unid); + return 0; + } + + if (!GetOverlappedResult(wm->dev_handle, &wm->hid_overlap, &b, 0)) + return 0; + } + + ResetEvent(wm->hid_overlap.hEvent);
+ return 1; +} + + +int wiiuse_io_write(struct wiimote_t* wm, byte* buf, int len) { + DWORD bytes; + int i;
+
+ if (!wm || !WIIMOTE_IS_CONNECTED(wm)) + return 0;
+
+ switch (wm->stack) { + case WIIUSE_STACK_UNKNOWN: + { + /* try to auto-detect the stack type */ + if (i = WriteFile(wm->dev_handle, buf, 22, &bytes, &wm->hid_overlap)) { + /* bluesoleil will always return 1 here, even if it's not connected */ + wm->stack = WIIUSE_STACK_BLUESOLEIL;
+ return i; + } + + if (i = HidD_SetOutputReport(wm->dev_handle, buf, len)) { + wm->stack = WIIUSE_STACK_MS; + return i; + } + + WIIUSE_ERROR("Unable to determine bluetooth stack type."); + return 0; + } + + case WIIUSE_STACK_MS: + return HidD_SetOutputReport(wm->dev_handle, buf, len); + + case WIIUSE_STACK_BLUESOLEIL: + return WriteFile(wm->dev_handle, buf, 22, &bytes, &wm->hid_overlap); + } + + return 0; +} + +#endif /* ifdef WIN32 */ diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/ir.c b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/ir.c new file mode 100644 index 0000000000..7a9bb68289 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/ir.c @@ -0,0 +1,748 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + +/** + * @file + * @brief Handles IR data. + */ + +#include <stdio.h> +#include <math.h> + +#ifndef WIN32 + #include <unistd.h> +#endif + +#include "definitions.h" +#include "wiiuse_internal.h" +#include "ir.h" + +static int get_ir_sens(struct wiimote_t* wm, char** block1, char** block2); +static void interpret_ir_data(struct wiimote_t* wm); +static void fix_rotated_ir_dots(struct ir_dot_t* dot, float ang); +static void get_ir_dot_avg(struct ir_dot_t* dot, int* x, int* y); +static void reorder_ir_dots(struct ir_dot_t* dot); +static float ir_distance(struct ir_dot_t* dot); +static int ir_correct_for_bounds(int* x, int* y, enum aspect_t aspect, int offset_x, int offset_y); +static void ir_convert_to_vres(int* x, int* y, enum aspect_t aspect, int vx, int vy); + + +/** + * @brief Set if the wiimote should track IR targets. + * + * @param wm Pointer to a wiimote_t structure. + * @param status 1 to enable, 0 to disable. + */ +void wiiuse_set_ir(struct wiimote_t* wm, int status) { + byte buf; + char* block1 = NULL; + char* block2 = NULL; + int ir_level; + + if (!wm) + return; + + /* + * Wait for the handshake to finish first. + * When it handshake finishes and sees that + * IR is enabled, it will call this function + * again to actually enable IR. + */ + if (!WIIMOTE_IS_SET(wm, WIIMOTE_STATE_HANDSHAKE_COMPLETE)) { + WIIUSE_DEBUG("Tried to enable IR, will wait until handshake finishes."); + WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_IR); + return; + } + + /* + * Check to make sure a sensitivity setting is selected. + */ + ir_level = get_ir_sens(wm, &block1, &block2); + if (!ir_level) { + WIIUSE_ERROR("No IR sensitivity setting selected."); + return; + } + + if (status) { + /* if already enabled then stop */ + if (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_IR)) + return; + WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_IR); + } else { + /* if already disabled then stop */ + if (!WIIMOTE_IS_SET(wm, WIIMOTE_STATE_IR)) + return; + WIIMOTE_DISABLE_STATE(wm, WIIMOTE_STATE_IR); + } + + /* set camera 1 and 2 */ + buf = (status ? 0x04 : 0x00); + wiiuse_send(wm, WM_CMD_IR, &buf, 1); + wiiuse_send(wm, WM_CMD_IR_2, &buf, 1); + + if (!status) { + WIIUSE_DEBUG("Disabled IR cameras for wiimote id %i.", wm->unid); + wiiuse_set_report_type(wm); + return; + } + + /* enable IR, set sensitivity */ + buf = 0x08; + wiiuse_write_data(wm, WM_REG_IR, &buf, 1); + + /* wait for the wiimote to catch up */ + #ifndef WIN32 + usleep(50000); + #else + Sleep(50); + #endif + + /* write sensitivity blocks */ + wiiuse_write_data(wm, WM_REG_IR_BLOCK1, (byte*)block1, 9); + wiiuse_write_data(wm, WM_REG_IR_BLOCK2, (byte*)block2, 2); + + /* set the IR mode */ + if (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_EXP)) + buf = WM_IR_TYPE_BASIC; + else + buf = WM_IR_TYPE_EXTENDED; + wiiuse_write_data(wm, WM_REG_IR_MODENUM, &buf, 1); + + #ifndef WIN32 + usleep(50000); + #else + Sleep(50); + #endif + + /* set the wiimote report type */ + wiiuse_set_report_type(wm); + + WIIUSE_DEBUG("Enabled IR camera for wiimote id %i (sensitivity level %i).", wm->unid, ir_level); +} + + +/** + * @brief Get the IR sensitivity settings. + * + * @param wm Pointer to a wiimote_t structure. + * @param block1 [out] Pointer to where block1 will be set. + * @param block2 [out] Pointer to where block2 will be set. + * + * @return Returns the sensitivity level. + */ +static int get_ir_sens(struct wiimote_t* wm, char** block1, char** block2) { + if (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_IR_SENS_LVL1)) { + *block1 = WM_IR_BLOCK1_LEVEL1; + *block2 = WM_IR_BLOCK2_LEVEL1; + return 1; + } else if (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_IR_SENS_LVL2)) { + *block1 = WM_IR_BLOCK1_LEVEL2; + *block2 = WM_IR_BLOCK2_LEVEL2; + return 2; + } else if (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_IR_SENS_LVL3)) { + *block1 = WM_IR_BLOCK1_LEVEL3; + *block2 = WM_IR_BLOCK2_LEVEL3; + return 3; + } else if (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_IR_SENS_LVL4)) { + *block1 = WM_IR_BLOCK1_LEVEL4; + *block2 = WM_IR_BLOCK2_LEVEL4; + return 4; + } else if (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_IR_SENS_LVL5)) { + *block1 = WM_IR_BLOCK1_LEVEL5; + *block2 = WM_IR_BLOCK2_LEVEL5; + return 5; + } + + *block1 = NULL; + *block2 = NULL; + return 0; +} + + +/** + * @brief Set the virtual screen resolution for IR tracking. + * + * @param wm Pointer to a wiimote_t structure. + * @param status 1 to enable, 0 to disable. + */ +void wiiuse_set_ir_vres(struct wiimote_t* wm, unsigned int x, unsigned int y) { + if (!wm) return; + + wm->ir.vres[0] = (x-1); + wm->ir.vres[1] = (y-1); +} + + +/** + * @brief Set the XY position for the IR cursor. + * + * @param wm Pointer to a wiimote_t structure. + */ +void wiiuse_set_ir_position(struct wiimote_t* wm, enum ir_position_t pos) { + if (!wm) return; + + wm->ir.pos = pos; + + switch (pos) { + + case WIIUSE_IR_ABOVE: + wm->ir.offset[0] = 0; + + if (wm->ir.aspect == WIIUSE_ASPECT_16_9) + wm->ir.offset[1] = WM_ASPECT_16_9_Y/2 - 70; + else if (wm->ir.aspect == WIIUSE_ASPECT_4_3) + wm->ir.offset[1] = WM_ASPECT_4_3_Y/2 - 100; + + return; + + case WIIUSE_IR_BELOW: + wm->ir.offset[0] = 0; + + if (wm->ir.aspect == WIIUSE_ASPECT_16_9) + wm->ir.offset[1] = -WM_ASPECT_16_9_Y/2 + 100; + else if (wm->ir.aspect == WIIUSE_ASPECT_4_3) + wm->ir.offset[1] = -WM_ASPECT_4_3_Y/2 + 70; + + return; + + default: + return; + }; +} + + +/** + * @brief Set the aspect ratio of the TV/monitor. + * + * @param wm Pointer to a wiimote_t structure. + * @param aspect Either WIIUSE_ASPECT_16_9 or WIIUSE_ASPECT_4_3 + */ +void wiiuse_set_aspect_ratio(struct wiimote_t* wm, enum aspect_t aspect) { + if (!wm) return; + + wm->ir.aspect = aspect; + + if (aspect == WIIUSE_ASPECT_4_3) { + wm->ir.vres[0] = WM_ASPECT_4_3_X; + wm->ir.vres[1] = WM_ASPECT_4_3_Y; + } else { + wm->ir.vres[0] = WM_ASPECT_16_9_X; + wm->ir.vres[1] = WM_ASPECT_16_9_Y; + } + + /* reset the position offsets */ + wiiuse_set_ir_position(wm, wm->ir.pos); +} + + +/** + * @brief Set the IR sensitivity. + * + * @param wm Pointer to a wiimote_t structure. + * @param level 1-5, same as Wii system sensitivity setting. + * + * If the level is < 1, then level will be set to 1. + * If the level is > 5, then level will be set to 5. + */ +void wiiuse_set_ir_sensitivity(struct wiimote_t* wm, int level) { + char* block1 = NULL; + char* block2 = NULL; + + if (!wm) return; + + if (level > 5) level = 5; + if (level < 1) level = 1; + + WIIMOTE_DISABLE_STATE(wm, (WIIMOTE_STATE_IR_SENS_LVL1 | + WIIMOTE_STATE_IR_SENS_LVL2 | + WIIMOTE_STATE_IR_SENS_LVL3 | + WIIMOTE_STATE_IR_SENS_LVL4 | + WIIMOTE_STATE_IR_SENS_LVL5)); + + switch (level) { + case 1: + WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_IR_SENS_LVL1); + break; + case 2: + WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_IR_SENS_LVL2); + break; + case 3: + WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_IR_SENS_LVL3); + break; + case 4: + WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_IR_SENS_LVL4); + break; + case 5: + WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_IR_SENS_LVL5); + break; + default: + return; + } + + /* set the new sensitivity */ + get_ir_sens(wm, &block1, &block2); + + wiiuse_write_data(wm, WM_REG_IR_BLOCK1, (byte*)block1, 9); + wiiuse_write_data(wm, WM_REG_IR_BLOCK2, (byte*)block2, 2); + + WIIUSE_DEBUG("Set IR sensitivity to level %i (unid %i)", level, wm->unid); +} + + +/** + * @brief Calculate the data from the IR spots. Basic IR mode. + * + * @param wm Pointer to a wiimote_t structure. + * @param data Data returned by the wiimote for the IR spots. + */ +void calculate_basic_ir(struct wiimote_t* wm, byte* data) { + struct ir_dot_t* dot = wm->ir.dot; + int i; + + dot[0].rx = 1023 - (data[0] | ((data[2] & 0x30) << 4)); + dot[0].ry = data[1] | ((data[2] & 0xC0) << 2); + + dot[1].rx = 1023 - (data[3] | ((data[2] & 0x03) << 8)); + dot[1].ry = data[4] | ((data[2] & 0x0C) << 6); + + dot[2].rx = 1023 - (data[5] | ((data[7] & 0x30) << 4)); + dot[2].ry = data[6] | ((data[7] & 0xC0) << 2); + + dot[3].rx = 1023 - (data[8] | ((data[7] & 0x03) << 8)); + dot[3].ry = data[9] | ((data[7] & 0x0C) << 6); + + /* set each IR spot to visible if spot is in range */ + for (i = 0; i < 4; ++i) { + if (dot[i].ry == 1023) + dot[i].visible = 0; + else { + dot[i].visible = 1; + dot[i].size = 0; /* since we don't know the size, set it as 0 */ + } + } + + interpret_ir_data(wm); +} + + +/** + * @brief Calculate the data from the IR spots. Extended IR mode. + * + * @param wm Pointer to a wiimote_t structure. + * @param data Data returned by the wiimote for the IR spots. + */ +void calculate_extended_ir(struct wiimote_t* wm, byte* data) { + struct ir_dot_t* dot = wm->ir.dot; + int i; + + for (i = 0; i < 4; ++i) { + dot[i].rx = 1023 - (data[3*i] | ((data[(3*i)+2] & 0x30) << 4)); + dot[i].ry = data[(3*i)+1] | ((data[(3*i)+2] & 0xC0) << 2); + + dot[i].size = data[(3*i)+2] & 0x0F; + + /* if in range set to visible */ + if (dot[i].ry == 1023) + dot[i].visible = 0; + else + dot[i].visible = 1; + } + + interpret_ir_data(wm); +} + + +/** + * @brief Interpret IR data into more user friendly variables. + * + * @param wm Pointer to a wiimote_t structure. + */ +static void interpret_ir_data(struct wiimote_t* wm) { + struct ir_dot_t* dot = wm->ir.dot; + int i; + float roll = 0.0f; + int last_num_dots = wm->ir.num_dots; + + if (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_ACC)) + roll = wm->orient.roll; + + /* count visible dots */ + wm->ir.num_dots = 0; + for (i = 0; i < 4; ++i) { + if (dot[i].visible) + wm->ir.num_dots++; + } + + switch (wm->ir.num_dots) { + case 0: + { + wm->ir.state = 0; + + /* reset the dot ordering */ + for (i = 0; i < 4; ++i) + dot[i].order = 0; + + wm->ir.x = 0; + wm->ir.y = 0; + wm->ir.z = 0.0f; + + return; + } + case 1: + { + fix_rotated_ir_dots(wm->ir.dot, roll); + + if (wm->ir.state < 2) { + /* + * Only 1 known dot, so use just that. + */ + for (i = 0; i < 4; ++i) { + if (dot[i].visible) { + wm->ir.x = dot[i].x; + wm->ir.y = dot[i].y; + + wm->ir.ax = wm->ir.x; + wm->ir.ay = wm->ir.y; + + /* can't calculate yaw because we don't have the distance */ + //wm->orient.yaw = calc_yaw(&wm->ir); + + ir_convert_to_vres(&wm->ir.x, &wm->ir.y, wm->ir.aspect, wm->ir.vres[0], wm->ir.vres[1]); + break; + } + } + } else { + /* + * Only see 1 dot but know theres 2. + * Try to estimate where the other one + * should be and use that. + */ + for (i = 0; i < 4; ++i) { + if (dot[i].visible) { + int ox = 0; + int x, y; + + if (dot[i].order == 1) + /* visible is the left dot - estimate where the right is */ + ox = dot[i].x + wm->ir.distance; + else if (dot[i].order == 2) + /* visible is the right dot - estimate where the left is */ + ox = dot[i].x - wm->ir.distance; + + x = ((signed int)dot[i].x + ox) / 2; + y = dot[i].y; + + wm->ir.ax = x; + wm->ir.ay = y; + wm->orient.yaw = calc_yaw(&wm->ir); + + if (ir_correct_for_bounds(&x, &y, wm->ir.aspect, wm->ir.offset[0], wm->ir.offset[1])) { + ir_convert_to_vres(&x, &y, wm->ir.aspect, wm->ir.vres[0], wm->ir.vres[1]); + wm->ir.x = x; + wm->ir.y = y; + } + + break; + } + } + } + + break; + } + case 2: + case 3: + case 4: + { + /* + * Two (or more) dots known and seen. + * Average them together to estimate the true location. + */ + int x, y; + wm->ir.state = 2; + + fix_rotated_ir_dots(wm->ir.dot, roll); + + /* if there is at least 1 new dot, reorder them all */ + if (wm->ir.num_dots > last_num_dots) { + reorder_ir_dots(dot); + wm->ir.x = 0; + wm->ir.y = 0; + } + + wm->ir.distance = ir_distance(dot); + wm->ir.z = 1023 - wm->ir.distance; + + get_ir_dot_avg(wm->ir.dot, &x, &y); + + wm->ir.ax = x; + wm->ir.ay = y; + wm->orient.yaw = calc_yaw(&wm->ir); + + if (ir_correct_for_bounds(&x, &y, wm->ir.aspect, wm->ir.offset[0], wm->ir.offset[1])) { + ir_convert_to_vres(&x, &y, wm->ir.aspect, wm->ir.vres[0], wm->ir.vres[1]); + wm->ir.x = x; + wm->ir.y = y; + } + + break; + } + default: + { + break; + } + } + + #ifdef WITH_WIIUSE_DEBUG + { + int ir_level; + WIIUSE_GET_IR_SENSITIVITY(wm, &ir_level); + WIIUSE_DEBUG("IR sensitivity: %i", ir_level); + WIIUSE_DEBUG("IR visible dots: %i", wm->ir.num_dots); + for (i = 0; i < 4; ++i) + if (dot[i].visible) + WIIUSE_DEBUG("IR[%i][order %i] (%.3i, %.3i) -> (%.3i, %.3i)", i, dot[i].order, dot[i].rx, dot[i].ry, dot[i].x, dot[i].y); + WIIUSE_DEBUG("IR[absolute]: (%i, %i)", wm->ir.x, wm->ir.y); + } + #endif +} + + + +/** + * @brief Fix the rotation of the IR dots. + * + * @param dot An array of 4 ir_dot_t objects. + * @param ang The roll angle to correct by (-180, 180) + * + * If there is roll then the dots are rotated + * around the origin and give a false cursor + * position. Correct for the roll. + * + * If the accelerometer is off then obviously + * this will not do anything and the cursor + * position may be inaccurate. + */ +static void fix_rotated_ir_dots(struct ir_dot_t* dot, float ang) { + float s, c; + int x, y; + int i; + + if (!ang) { + for (i = 0; i < 4; ++i) { + dot[i].x = dot[i].rx; + dot[i].y = dot[i].ry; + } + return; + } + + s = sin(DEGREE_TO_RAD(ang)); + c = cos(DEGREE_TO_RAD(ang)); + + /* + * [ cos(theta) -sin(theta) ][ ir->rx ] + * [ sin(theta) cos(theta) ][ ir->ry ] + */ + + for (i = 0; i < 4; ++i) { + if (!dot[i].visible) + continue; + + x = dot[i].rx - (1024/2); + y = dot[i].ry - (768/2); + + dot[i].x = (c * x) + (-s * y); + dot[i].y = (s * x) + (c * y); + + dot[i].x += (1024/2); + dot[i].y += (768/2); + } +} + + +/** + * @brief Average IR dots. + * + * @param dot An array of 4 ir_dot_t objects. + * @param x [out] Average X + * @param y [out] Average Y + */ +static void get_ir_dot_avg(struct ir_dot_t* dot, int* x, int* y) { + int vis = 0, i = 0; + + *x = 0; + *y = 0; + + for (; i < 4; ++i) { + if (dot[i].visible) { + *x += dot[i].x; + *y += dot[i].y; + ++vis; + } + } + + *x /= vis; + *y /= vis; +} + + +/** + * @brief Reorder the IR dots. + * + * @param dot An array of 4 ir_dot_t objects. + */ +static void reorder_ir_dots(struct ir_dot_t* dot) { + int i, j, order; + + /* reset the dot ordering */ + for (i = 0; i < 4; ++i) + dot[i].order = 0; + + for (order = 1; order < 5; ++order) { + i = 0; + + for (; !dot[i].visible || dot[i].order; ++i) + if (i > 4) + return; + + for (j = 0; j < 4; ++j) { + if (dot[j].visible && !dot[j].order && (dot[j].x < dot[i].x)) + i = j; + } + + dot[i].order = order; + } +} + + +/** + * @brief Calculate the distance between the first 2 visible IR dots. + * + * @param dot An array of 4 ir_dot_t objects. + */ +static float ir_distance(struct ir_dot_t* dot) { + int i1, i2; + int xd, yd; + + for (i1 = 0; i1 < 4; ++i1) + if (dot[i1].visible) + break; + if (i1 == 4) + return 0.0f; + + for (i2 = i1+1; i2 < 4; ++i2) + if (dot[i2].visible) + break; + if (i2 == 4) + return 0.0f; + + xd = dot[i2].x - dot[i1].x; + yd = dot[i2].y - dot[i1].y; + + return sqrt(xd*xd + yd*yd); +} + + +/** + * @brief Correct for the IR bounding box. + * + * @param x [out] The current X, it will be updated if valid. + * @param y [out] The current Y, it will be updated if valid. + * @param aspect Aspect ratio of the screen. + * @param offset_x The X offset of the bounding box. + * @param offset_y The Y offset of the bounding box. + * + * @return Returns 1 if the point is valid and was updated. + * + * Nintendo was smart with this bit. They sacrifice a little + * precision for a big increase in usability. + */ +static int ir_correct_for_bounds(int* x, int* y, enum aspect_t aspect, int offset_x, int offset_y) { + int x0, y0; + int xs, ys; + + if (aspect == WIIUSE_ASPECT_16_9) { + xs = WM_ASPECT_16_9_X; + ys = WM_ASPECT_16_9_Y; + } else { + xs = WM_ASPECT_4_3_X; + ys = WM_ASPECT_4_3_Y; + } + + x0 = ((1024 - xs) / 2) + offset_x; + y0 = ((768 - ys) / 2) + offset_y; + + if ((*x >= x0) + && (*x <= (x0 + xs)) + && (*y >= y0) + && (*y <= (y0 + ys))) + { + *x -= offset_x; + *y -= offset_y; + + return 1; + } + + return 0; +} + + +/** + * @brief Interpolate the point to the user defined virtual screen resolution. + */ +static void ir_convert_to_vres(int* x, int* y, enum aspect_t aspect, int vx, int vy) { + int xs, ys; + + if (aspect == WIIUSE_ASPECT_16_9) { + xs = WM_ASPECT_16_9_X; + ys = WM_ASPECT_16_9_Y; + } else { + xs = WM_ASPECT_4_3_X; + ys = WM_ASPECT_4_3_Y; + } + + *x -= ((1024-xs)/2); + *y -= ((768-ys)/2); + + *x = (*x / (float)xs) * vx; + *y = (*y / (float)ys) * vy; +} + + +/** + * @brief Calculate yaw given the IR data. + * + * @param ir IR data structure. + */ +float calc_yaw(struct ir_t* ir) { + float x; + + x = ir->ax - 512; + x = x * (ir->z / 1024.0f); + + return RAD_TO_DEGREE( atanf(x / ir->z) ); +} diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/ir.h b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/ir.h new file mode 100644 index 0000000000..9082492987 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/ir.h @@ -0,0 +1,56 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + +/** + * @file + * @brief Handles IR data. + */ + +#ifndef IR_H_INCLUDED +#define IR_H_INCLUDED + +#include "wiiuse_internal.h" + +#define WII_VRES_X 560 +#define WII_VRES_Y 340 + +#ifdef __cplusplus +extern "C" { +#endif + +void calculate_basic_ir(struct wiimote_t* wm, byte* data); +void calculate_extended_ir(struct wiimote_t* wm, byte* data); +float calc_yaw(struct ir_t* ir); + +#ifdef __cplusplus +} +#endif + +#endif // IR_H_INCLUDED + + diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/msvc/wiiuse.dsp b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/msvc/wiiuse.dsp new file mode 100644 index 0000000000..dd66ae7078 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/msvc/wiiuse.dsp @@ -0,0 +1,191 @@ +# Microsoft Developer Studio Project File - Name="wiiuse" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=wiiuse - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "wiiuse.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "wiiuse.mak" CFG="wiiuse - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "wiiuse - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "wiiuse - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "wiiuse - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "WIIUSE_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "WIIUSE_EXPORTS" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib Ws2_32.lib hid.lib setupapi.lib /nologo /dll /machine:I386
+
+!ELSEIF "$(CFG)" == "wiiuse - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "WIIUSE_EXPORTS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "WIIUSE_EXPORTS" /D "WITH_WIIUSE_DEBUG" /YX /FD /GZ /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib Ws2_32.lib hid.lib setupapi.lib /nologo /dll /debug /machine:I386 /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "wiiuse - Win32 Release"
+# Name "wiiuse - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=..\classic.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\dynamics.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\events.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\guitar_hero_3.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\io.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\io_nix.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\io_win.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\ir.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\nunchuk.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\wiiuse.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE=..\classic.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\definitions.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\dynamics.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\events.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\guitar_hero_3.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\io.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\ir.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\nunchuk.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\os.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\wiiuse.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\wiiuse_internal.h
+# End Source File
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# Begin Source File
+
+SOURCE=..\..\CHANGELOG
+# End Source File
+# End Group
+# End Target
+# End Project
diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/msvc/wiiuse.dsw b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/msvc/wiiuse.dsw new file mode 100644 index 0000000000..5d662bf0ea --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/msvc/wiiuse.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00
+# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
+
+###############################################################################
+
+Project: "wiiuse"=".\wiiuse.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Global:
+
+Package=<5>
+{{{
+}}}
+
+Package=<3>
+{{{
+}}}
+
+###############################################################################
+
diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/msvc/wiiuse.opt b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/msvc/wiiuse.opt Binary files differnew file mode 100644 index 0000000000..b6ebe4c479 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/msvc/wiiuse.opt diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/nunchuk.c b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/nunchuk.c new file mode 100644 index 0000000000..48dbe6a089 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/nunchuk.c @@ -0,0 +1,210 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + +/** + * @file + * @brief Nunchuk expansion device. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> + +#include "definitions.h" +#include "wiiuse_internal.h" +#include "dynamics.h" +#include "events.h" +#include "nunchuk.h" + +static void nunchuk_pressed_buttons(struct nunchuk_t* nc, byte now); + +/** + * @brief Handle the handshake data from the nunchuk. + * + * @param nc A pointer to a nunchuk_t structure. + * @param data The data read in from the device. + * @param len The length of the data block, in bytes. + * + * @return Returns 1 if handshake was successful, 0 if not. + */ +int nunchuk_handshake(struct wiimote_t* wm, struct nunchuk_t* nc, byte* data, unsigned short len) { + int i;
+ int offset = 0;
+
+ nc->btns = 0; + nc->btns_held = 0; + nc->btns_released = 0; + + /* set the smoothing to the same as the wiimote */ + nc->flags = &wm->flags; + nc->accel_calib.st_alpha = wm->accel_calib.st_alpha; + + /* decrypt data */ + for (i = 0; i < len; ++i) + data[i] = (data[i] ^ 0x17) + 0x17;
+
+ if (data[offset] == 0xFF) {
+ /*
+ * Sometimes the data returned here is not correct.
+ * This might happen because the wiimote is lagging
+ * behind our initialization sequence.
+ * To fix this just request the handshake again.
+ *
+ * Other times it's just the first 16 bytes are 0xFF,
+ * but since the next 16 bytes are the same, just use
+ * those.
+ */
+ if (data[offset + 16] == 0xFF) {
+ /* get the calibration data */
+ byte* handshake_buf = malloc(EXP_HANDSHAKE_LEN * sizeof(byte));
+
+ WIIUSE_DEBUG("Nunchuk handshake appears invalid, trying again.");
+ wiiuse_read_data_cb(wm, handshake_expansion, handshake_buf, WM_EXP_MEM_CALIBR, EXP_HANDSHAKE_LEN);
+
+ return 0;
+ } else
+ offset += 16;
+ } + + nc->accel_calib.cal_zero.x = data[offset + 0]; + nc->accel_calib.cal_zero.y = data[offset + 1]; + nc->accel_calib.cal_zero.z = data[offset + 2]; + nc->accel_calib.cal_g.x = data[offset + 4]; + nc->accel_calib.cal_g.y = data[offset + 5]; + nc->accel_calib.cal_g.z = data[offset + 6]; + nc->js.max.x = data[offset + 8]; + nc->js.min.x = data[offset + 9]; + nc->js.center.x = data[offset + 10]; + nc->js.max.y = data[offset + 11]; + nc->js.min.y = data[offset + 12]; + nc->js.center.y = data[offset + 13]; +
+ /* default the thresholds to the same as the wiimote */ + nc->orient_threshold = wm->orient_threshold; + nc->accel_threshold = wm->accel_threshold; + + /* handshake done */
+ wm->exp.type = EXP_NUNCHUK;
+
+ #ifdef WIN32
+ wm->timeout = WIIMOTE_DEFAULT_TIMEOUT;
+ #endif + + return 1; +} + + +/** + * @brief The nunchuk disconnected. + * + * @param nc A pointer to a nunchuk_t structure. + */ +void nunchuk_disconnected(struct nunchuk_t* nc) { + memset(nc, 0, sizeof(struct nunchuk_t)); +} + + + +/** + * @brief Handle nunchuk event. + * + * @param nc A pointer to a nunchuk_t structure. + * @param msg The message specified in the event packet. + */ +void nunchuk_event(struct nunchuk_t* nc, byte* msg) { + int i; + + /* decrypt data */ + for (i = 0; i < 6; ++i) + msg[i] = (msg[i] ^ 0x17) + 0x17; + + /* get button states */ + nunchuk_pressed_buttons(nc, msg[5]); + + /* calculate joystick state */ + calc_joystick_state(&nc->js, msg[0], msg[1]); + + /* calculate orientation */ + nc->accel.x = msg[2]; + nc->accel.y = msg[3]; + nc->accel.z = msg[4]; + + calculate_orientation(&nc->accel_calib, &nc->accel, &nc->orient, NUNCHUK_IS_FLAG_SET(nc, WIIUSE_SMOOTHING)); + calculate_gforce(&nc->accel_calib, &nc->accel, &nc->gforce); +} + + +/** + * @brief Find what buttons are pressed. + * + * @param nc Pointer to a nunchuk_t structure. + * @param msg The message byte specified in the event packet. + */ +static void nunchuk_pressed_buttons(struct nunchuk_t* nc, byte now) { + /* message is inverted (0 is active, 1 is inactive) */ + now = ~now & NUNCHUK_BUTTON_ALL; + + /* pressed now & were pressed, then held */ + nc->btns_held = (now & nc->btns); + + /* were pressed or were held & not pressed now, then released */ + nc->btns_released = ((nc->btns | nc->btns_held) & ~now); + + /* buttons pressed now */ + nc->btns = now; +} + + +/** + * @brief Set the orientation event threshold for the nunchuk. + * + * @param wm Pointer to a wiimote_t structure with a nunchuk attached. + * @param threshold The decimal place that should be considered a significant change. + * + * See wiiuse_set_orient_threshold() for details. + */ +void wiiuse_set_nunchuk_orient_threshold(struct wiimote_t* wm, float threshold) { + if (!wm) return; + + wm->exp.nunchuk.orient_threshold = threshold; +} + + +/** + * @brief Set the accelerometer event threshold for the nunchuk. + * + * @param wm Pointer to a wiimote_t structure with a nunchuk attached. + * @param threshold The decimal place that should be considered a significant change. + * + * See wiiuse_set_orient_threshold() for details. + */ +void wiiuse_set_nunchuk_accel_threshold(struct wiimote_t* wm, int threshold) { + if (!wm) return; + + wm->exp.nunchuk.accel_threshold = threshold; +} diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/nunchuk.h b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/nunchuk.h new file mode 100644 index 0000000000..f036073d64 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/nunchuk.h @@ -0,0 +1,53 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + +/** + * @file + * @brief Nunchuk expansion device. + */ + +#ifndef NUNCHUK_H_INCLUDED +#define NUNCHUK_H_INCLUDED + +#include "wiiuse_internal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int nunchuk_handshake(struct wiimote_t* wm, struct nunchuk_t* nc, byte* data, unsigned short len); + +void nunchuk_disconnected(struct nunchuk_t* nc); + +void nunchuk_event(struct nunchuk_t* nc, byte* msg); + +#ifdef __cplusplus +} +#endif + +#endif // NUNCHUK_H_INCLUDED diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/os.h b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/os.h new file mode 100644 index 0000000000..53b80f557c --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/os.h @@ -0,0 +1,56 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + + +/** + * @file + * @brief Operating system related definitions. + * + * This file is an attempt to separate operating system + * dependent functions and choose what should be used + * at compile time. + */ + +#ifndef OS_H_INCLUDED +#define OS_H_INCLUDED + +#ifdef WIN32
+ /* windows */ + #define isnan(x) _isnan(x) + #define isinf(x) !_finite(x) + + /* disable warnings I don't care about */
+ #pragma warning(disable:4244) /* possible loss of data conversion */
+ #pragma warning(disable:4273) /* inconsistent dll linkage */
+ #pragma warning(disable:4217) +#else + /* nix */ +#endif + + +#endif // OS_H_INCLUDED diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse.c b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse.c new file mode 100644 index 0000000000..8d4b763864 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse.c @@ -0,0 +1,764 @@ +/*
+ * wiiuse
+ *
+ * Written By:
+ * Michael Laforest < para >
+ * Email: < thepara (--AT--) g m a i l [--DOT--] com >
+ *
+ * Copyright 2006-2007
+ *
+ * This file is part of wiiuse.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * $Header$
+ *
+ */
+
+/**
+ * @file
+ * @brief General wiimote operations.
+ *
+ * The file includes functions that handle general
+ * tasks. Most of these are functions that are part
+ * of the API.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifndef WIN32
+ #include <unistd.h>
+#else
+ #include <Winsock2.h>
+#endif
+
+#include "definitions.h"
+#include "wiiuse_internal.h"
+#include "events.h"
+#include "io.h"
+
+static int g_banner = 0;
+
+/**
+ * @breif Returns the version of the library.
+ */
+const char* wiiuse_version() {
+ return WIIUSE_VERSION;
+}
+
+
+/**
+ * @brief Clean up wiimote_t array created by wiiuse_init()
+ */
+void wiiuse_cleanup(struct wiimote_t** wm, int wiimotes) {
+ int i = 0;
+
+ if (!wm)
+ return;
+
+ WIIUSE_INFO("wiiuse clean up...");
+
+ for (; i < wiimotes; ++i) {
+ wiiuse_disconnect(wm[i]);
+ free(wm[i]);
+ }
+
+ free(wm);
+
+ return;
+}
+
+
+/**
+ * @brief Initialize an array of wiimote structures.
+ *
+ * @param wiimotes Number of wiimote_t structures to create.
+ *
+ * @return An array of initialized wiimote_t structures.
+ *
+ * @see wiiuse_connect()
+ *
+ * The array returned by this function can be passed to various
+ * functions, including wiiuse_connect().
+ */
+struct wiimote_t** wiiuse_init(int wiimotes) {
+ int i = 0;
+ struct wiimote_t** wm = NULL;
+
+ /*
+ * Please do not remove this banner.
+ * GPL asks that you please leave output credits intact.
+ * Thank you.
+ *
+ * This banner is only displayed once so that if you need
+ * to call this function again it won't be intrusive.
+ */
+ if (!g_banner) {
+ printf( "wiiuse v" WIIUSE_VERSION " loaded.\n"
+ " By: Michael Laforest <thepara[at]gmail{dot}com>\n"
+ " http://wiiuse.net http://wiiuse.sf.net\n");
+ g_banner = 1;
+ }
+
+ if (!wiimotes)
+ return NULL;
+
+ wm = malloc(sizeof(struct wiimote_t*) * wiimotes);
+
+ for (i = 0; i < wiimotes; ++i) {
+ wm[i] = malloc(sizeof(struct wiimote_t));
+ memset(wm[i], 0, sizeof(struct wiimote_t));
+
+ wm[i]->unid = i+1;
+
+ #ifndef WIN32
+ wm[i]->bdaddr = *BDADDR_ANY;
+ wm[i]->out_sock = -1;
+ wm[i]->in_sock = -1;
+ #else
+ wm[i]->dev_handle = 0;
+ wm[i]->stack = WIIUSE_STACK_UNKNOWN;
+ wm[i]->normal_timeout = WIIMOTE_DEFAULT_TIMEOUT;
+ wm[i]->exp_timeout = WIIMOTE_EXP_TIMEOUT;
+ wm[i]->timeout = wm[i]->normal_timeout;
+ #endif
+
+ wm[i]->state = WIIMOTE_INIT_STATES;
+ wm[i]->flags = WIIUSE_INIT_FLAGS;
+
+ wm[i]->event = WIIUSE_NONE;
+
+ wm[i]->exp.type = EXP_NONE;
+
+ wiiuse_set_aspect_ratio(wm[i], WIIUSE_ASPECT_4_3);
+ wiiuse_set_ir_position(wm[i], WIIUSE_IR_ABOVE);
+
+ wm[i]->orient_threshold = 0.5f;
+ wm[i]->accel_threshold = 5;
+
+ wm[i]->accel_calib.st_alpha = WIIUSE_DEFAULT_SMOOTH_ALPHA;
+ }
+
+ return wm;
+}
+
+
+/**
+ * @brief The wiimote disconnected.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ */
+void wiiuse_disconnected(struct wiimote_t* wm) {
+ if (!wm) return;
+
+ WIIUSE_INFO("Wiimote disconnected [id %i].", wm->unid);
+
+ /* disable the connected flag */
+ WIIMOTE_DISABLE_STATE(wm, WIIMOTE_STATE_CONNECTED);
+
+ /* reset a bunch of stuff */
+ #ifndef WIN32
+ wm->out_sock = -1;
+ wm->in_sock = -1;
+ #else
+ wm->dev_handle = 0;
+ #endif
+
+ wm->leds = 0;
+ wm->state = WIIMOTE_INIT_STATES;
+ wm->read_req = NULL;
+ wm->handshake_state = 0;
+ wm->btns = 0;
+ wm->btns_held = 0;
+ wm->btns_released = 0;
+ memset(wm->event_buf, 0, sizeof(wm->event_buf));
+
+ wm->event = WIIUSE_DISCONNECT;
+}
+
+
+/**
+ * @brief Enable or disable the rumble.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ * @param status 1 to enable, 0 to disable.
+ */
+void wiiuse_rumble(struct wiimote_t* wm, int status) {
+ byte buf;
+
+ if (!wm || !WIIMOTE_IS_CONNECTED(wm))
+ return;
+
+ /* make sure to keep the current lit leds */
+ buf = wm->leds;
+
+ if (status) {
+ WIIUSE_DEBUG("Starting rumble...");
+ WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_RUMBLE);
+ buf |= 0x01;
+ } else {
+ WIIUSE_DEBUG("Stopping rumble...");
+ WIIMOTE_DISABLE_STATE(wm, WIIMOTE_STATE_RUMBLE);
+ }
+
+ /* preserve IR state */
+ if (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_IR))
+ buf |= 0x04;
+
+ wiiuse_send(wm, WM_CMD_RUMBLE, &buf, 1);
+}
+
+
+/**
+ * @brief Toggle the state of the rumble.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ */
+void wiiuse_toggle_rumble(struct wiimote_t* wm) {
+ if (!wm) return;
+
+ wiiuse_rumble(wm, !WIIMOTE_IS_SET(wm, WIIMOTE_STATE_RUMBLE));
+}
+
+
+/**
+ * @brief Set the enabled LEDs.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ * @param leds What LEDs to enable.
+ *
+ * \a leds is a bitwise or of WIIMOTE_LED_1, WIIMOTE_LED_2, WIIMOTE_LED_3, or WIIMOTE_LED_4.
+ */
+void wiiuse_set_leds(struct wiimote_t* wm, int leds) {
+ byte buf;
+
+ if (!wm || !WIIMOTE_IS_CONNECTED(wm))
+ return;
+
+ /* remove the lower 4 bits because they control rumble */
+ wm->leds = (leds & 0xF0);
+
+ /* make sure if the rumble is on that we keep it on */
+ if (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_RUMBLE))
+ wm->leds |= 0x01;
+
+ buf = wm->leds;
+
+ wiiuse_send(wm, WM_CMD_LED, &buf, 1);
+}
+
+
+/**
+ * @brief Set if the wiimote should report motion sensing.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ * @param status 1 to enable, 0 to disable.
+ *
+ * Since reporting motion sensing sends a lot of data,
+ * the wiimote saves power by not transmitting it
+ * by default.
+ */
+void wiiuse_motion_sensing(struct wiimote_t* wm, int status) {
+ if (status)
+ WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_ACC);
+ else
+ WIIMOTE_DISABLE_STATE(wm, WIIMOTE_STATE_ACC);
+
+ wiiuse_set_report_type(wm);
+}
+
+
+/**
+ * @brief Set the report type based on the current wiimote state.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ *
+ * @return The report type sent.
+ *
+ * The wiimote reports formatted packets depending on the
+ * report type that was last requested. This function will
+ * update the type of report that should be sent based on
+ * the current state of the device.
+ */
+int wiiuse_set_report_type(struct wiimote_t* wm) {
+ byte buf[2];
+ int motion, exp, ir;
+
+ if (!wm || !WIIMOTE_IS_CONNECTED(wm))
+ return 0;
+
+ buf[0] = (WIIMOTE_IS_FLAG_SET(wm, WIIUSE_CONTINUOUS) ? 0x04 : 0x00); /* set to 0x04 for continuous reporting */
+ buf[1] = 0x00;
+
+ /* if rumble is enabled, make sure we keep it */
+ if (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_RUMBLE))
+ buf[0] |= 0x01;
+
+ motion = WIIMOTE_IS_SET(wm, WIIMOTE_STATE_ACC);
+ exp = WIIMOTE_IS_SET(wm, WIIMOTE_STATE_EXP);
+ ir = WIIMOTE_IS_SET(wm, WIIMOTE_STATE_IR);
+
+ if (motion && ir && exp) buf[1] = WM_RPT_BTN_ACC_IR_EXP;
+ else if (motion && exp) buf[1] = WM_RPT_BTN_ACC_EXP;
+ else if (motion && ir) buf[1] = WM_RPT_BTN_ACC_IR;
+ else if (ir && exp) buf[1] = WM_RPT_BTN_IR_EXP;
+ else if (ir) buf[1] = WM_RPT_BTN_ACC_IR;
+ else if (exp) buf[1] = WM_RPT_BTN_EXP;
+ else if (motion) buf[1] = WM_RPT_BTN_ACC;
+ else buf[1] = WM_RPT_BTN;
+
+ WIIUSE_DEBUG("Setting report type: 0x%x", buf[1]);
+
+ exp = wiiuse_send(wm, WM_CMD_REPORT_TYPE, buf, 2);
+ if (exp <= 0)
+ return exp;
+
+ return buf[1];
+}
+
+
+/**
+ * @brief Read data from the wiimote (callback version).
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ * @param read_cb Function pointer to call when the data arrives from the wiimote.
+ * @param buffer An allocated buffer to store the data as it arrives from the wiimote.
+ * Must be persistent in memory and large enough to hold the data.
+ * @param addr The address of wiimote memory to read from.
+ * @param len The length of the block to be read.
+ *
+ * The library can only handle one data read request at a time
+ * because it must keep track of the buffer and other
+ * events that are specific to that request. So if a request
+ * has already been made, subsequent requests will be added
+ * to a pending list and be sent out when the previous
+ * finishes.
+ */
+int wiiuse_read_data_cb(struct wiimote_t* wm, wiiuse_read_cb read_cb, byte* buffer, unsigned int addr, unsigned short len) {
+ struct read_req_t* req;
+
+ if (!wm || !WIIMOTE_IS_CONNECTED(wm))
+ return 0;
+ if (!buffer || !len || !read_cb)
+ return 0;
+
+ /* make this request structure */
+ req = (struct read_req_t*)malloc(sizeof(struct read_req_t));
+ req->cb = read_cb;
+ req->buf = buffer;
+ req->addr = addr;
+ req->size = len;
+ req->wait = len; + req->dirty = 0;
+ req->next = NULL;
+
+ /* add this to the request list */
+ if (!wm->read_req) {
+ /* root node */
+ wm->read_req = req;
+
+ WIIUSE_DEBUG("Data read request can be sent out immediately.");
+
+ /* send the request out immediately */
+ wiiuse_send_next_pending_read_request(wm);
+ } else {
+ struct read_req_t* nptr = wm->read_req;
+ for (; nptr->next; nptr = nptr->next);
+ nptr->next = req;
+
+ WIIUSE_DEBUG("Added pending data read request.");
+ }
+
+ return 1;
+}
+
+
+/** + * @brief Read data from the wiimote (event version). + * + * @param wm Pointer to a wiimote_t structure. + * @param buffer An allocated buffer to store the data as it arrives from the wiimote. + * Must be persistent in memory and large enough to hold the data. + * @param addr The address of wiimote memory to read from. + * @param len The length of the block to be read. + * + * The library can only handle one data read request at a time + * because it must keep track of the buffer and other + * events that are specific to that request. So if a request + * has already been made, subsequent requests will be added + * to a pending list and be sent out when the previous + * finishes. + */ +int wiiuse_read_data(struct wiimote_t* wm, byte* buffer, unsigned int addr, unsigned short len) { + struct read_req_t* req; + + if (!wm || !WIIMOTE_IS_CONNECTED(wm)) + return 0; + if (!buffer || !len) + return 0; + + /* make this request structure */ + req = (struct read_req_t*)malloc(sizeof(struct read_req_t)); + req->cb = NULL; + req->buf = buffer; + req->addr = addr; + req->size = len; + req->wait = len; + req->dirty = 0; + req->next = NULL; + + /* add this to the request list */ + if (!wm->read_req) { + /* root node */ + wm->read_req = req; + + WIIUSE_DEBUG("Data read request can be sent out immediately."); + + /* send the request out immediately */ + wiiuse_send_next_pending_read_request(wm); + } else { + struct read_req_t* nptr = wm->read_req; + for (; nptr->next; nptr = nptr->next); + nptr->next = req; + + WIIUSE_DEBUG("Added pending data read request."); + } + + return 1; +} + + +/**
+ * @brief Send the next pending data read request to the wiimote.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ *
+ * @see wiiuse_read_data()
+ *
+ * This function is not part of the wiiuse API.
+ */
+void wiiuse_send_next_pending_read_request(struct wiimote_t* wm) {
+ byte buf[6];
+ struct read_req_t* req;
+
+ if (!wm || !WIIMOTE_IS_CONNECTED(wm))
+ return;
+ if (!wm->read_req) return;
+
+ /* skip over dirty ones since they have already been read */ + req = wm->read_req; + while (req && req->dirty) + req = req->next; + if (!req) + return;
+
+ /* the offset is in big endian */
+ *(int*)(buf) = BIG_ENDIAN_LONG(req->addr);
+
+ /* the length is in big endian */
+ *(short*)(buf + 4) = BIG_ENDIAN_SHORT(req->size);
+
+ WIIUSE_DEBUG("Request read at address: 0x%x length: %i", req->addr, req->size);
+ wiiuse_send(wm, WM_CMD_READ_DATA, buf, 6);
+}
+
+
+/**
+ * @brief Request the wiimote controller status.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ *
+ * Controller status includes: battery level, LED status, expansions
+ */
+void wiiuse_status(struct wiimote_t* wm) {
+ byte buf = 0;
+
+ if (!wm || !WIIMOTE_IS_CONNECTED(wm))
+ return;
+
+ WIIUSE_DEBUG("Requested wiimote status.");
+
+ wiiuse_send(wm, WM_CMD_CTRL_STATUS, &buf, 1);
+}
+
+
+/**
+ * @brief Find a wiimote_t structure by its unique identifier.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ * @param wiimotes The number of wiimote_t structures in \a wm.
+ * @param unid The unique identifier to search for.
+ *
+ * @return Pointer to a wiimote_t structure, or NULL if not found.
+ */
+struct wiimote_t* wiiuse_get_by_id(struct wiimote_t** wm, int wiimotes, int unid) {
+ int i = 0;
+
+ for (; i < wiimotes; ++i) {
+ if (wm[i]->unid == unid)
+ return wm[i];
+ }
+
+ return NULL;
+}
+
+
+/**
+ * @brief Write data to the wiimote.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ * @param addr The address to write to.
+ * @param data The data to be written to the memory location.
+ * @param len The length of the block to be written.
+ */
+int wiiuse_write_data(struct wiimote_t* wm, unsigned int addr, byte* data, byte len) {
+ byte buf[21] = {0}; /* the payload is always 23 */
+
+ if (!wm || !WIIMOTE_IS_CONNECTED(wm))
+ return 0;
+ if (!data || !len)
+ return 0;
+
+ WIIUSE_DEBUG("Writing %i bytes to memory location 0x%x...", len, addr);
+
+ #ifdef WITH_WIIUSE_DEBUG
+ {
+ int i = 0;
+ printf("Write data is: ");
+ for (; i < len; ++i)
+ printf("%x ", data[i]);
+ printf("\n");
+ }
+ #endif
+
+ /* the offset is in big endian */
+ *(int*)(buf) = BIG_ENDIAN_LONG(addr);
+
+ /* length */
+ *(byte*)(buf + 4) = len;
+
+ /* data */
+ memcpy(buf + 5, data, len);
+
+ wiiuse_send(wm, WM_CMD_WRITE_DATA, buf, 21);
+ return 1;
+}
+
+
+/**
+ * @brief Send a packet to the wiimote.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ * @param report_type The report type to send (WIIMOTE_CMD_LED, WIIMOTE_CMD_RUMBLE, etc). Found in wiiuse.h
+ * @param msg The payload.
+ * @param len Length of the payload in bytes.
+ *
+ * This function should replace any write()s directly to the wiimote device.
+ */
+int wiiuse_send(struct wiimote_t* wm, byte report_type, byte* msg, int len) {
+ byte buf[32]; /* no payload is better than this */
+ int rumble = 0;
+
+ #ifndef WIN32
+ buf[0] = WM_SET_REPORT | WM_BT_OUTPUT;
+ buf[1] = report_type;
+ #else
+ buf[0] = report_type;
+ #endif
+
+ switch (report_type) {
+ case WM_CMD_LED:
+ case WM_CMD_RUMBLE:
+ case WM_CMD_CTRL_STATUS:
+ {
+ /* Rumble flag for: 0x11, 0x13, 0x14, 0x15, 0x19 or 0x1a */
+ if (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_RUMBLE))
+ rumble = 1;
+ break;
+ }
+ default:
+ break;
+ }
+
+ #ifndef WIN32
+ memcpy(buf+2, msg, len);
+ if (rumble)
+ buf[2] |= 0x01;
+ #else
+ memcpy(buf+1, msg, len);
+ if (rumble)
+ buf[1] |= 0x01;
+ #endif
+
+ #ifdef WITH_WIIUSE_DEBUG
+ {
+ int x = 2;
+ printf("[DEBUG] (id %i) SEND: (%x) %.2x ", wm->unid, buf[0], buf[1]);
+ #ifndef WIN32
+ for (; x < len+2; ++x)
+ #else
+ for (; x < len+1; ++x)
+ #endif
+ printf("%.2x ", buf[x]);
+ printf("\n");
+ }
+ #endif
+
+ #ifndef WIN32
+ return wiiuse_io_write(wm, buf, len+2);
+ #else
+ return wiiuse_io_write(wm, buf, len+1);
+ #endif
+}
+
+
+/**
+ * @brief Set flags for the specified wiimote.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ * @param enable Flags to enable.
+ * @param disable Flags to disable.
+ *
+ * @return The flags set after 'enable' and 'disable' have been applied.
+ *
+ * The values 'enable' and 'disable' may be any flags OR'ed together.
+ * Flags are defined in wiiuse.h.
+ */
+int wiiuse_set_flags(struct wiimote_t* wm, int enable, int disable) {
+ if (!wm) return 0;
+
+ /* remove mutually exclusive flags */
+ enable &= ~disable;
+ disable &= ~enable;
+
+ wm->flags |= enable;
+ wm->flags &= ~disable;
+
+ return wm->flags;
+}
+
+
+/**
+ * @brief Set the wiimote smoothing alpha value.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ * @param alpha The alpha value to set. Between 0 and 1.
+ *
+ * @return Returns the old alpha value.
+ *
+ * The alpha value is between 0 and 1 and is used in an exponential
+ * smoothing algorithm.
+ *
+ * Smoothing is only performed if the WIIMOTE_USE_SMOOTHING is set.
+ */
+float wiiuse_set_smooth_alpha(struct wiimote_t* wm, float alpha) {
+ float old;
+
+ if (!wm) return 0.0f;
+
+ old = wm->accel_calib.st_alpha;
+
+ wm->accel_calib.st_alpha = alpha;
+
+ /* if there is a nunchuk set that too */
+ if (wm->exp.type == EXP_NUNCHUK)
+ wm->exp.nunchuk.accel_calib.st_alpha = alpha;
+
+ return old;
+}
+
+
+/**
+ * @brief Set the bluetooth stack type to use.
+ *
+ * @param wm Array of wiimote_t structures.
+ * @param wiimotes Number of objects in the wm array.
+ * @param type The type of bluetooth stack to use.
+ */
+void wiiuse_set_bluetooth_stack(struct wiimote_t** wm, int wiimotes, enum win_bt_stack_t type) {
+ #ifdef WIN32
+ int i;
+
+ if (!wm) return;
+
+ for (i = 0; i < wiimotes; ++i)
+ wm[i]->stack = type;
+ #endif
+}
+
+
+/**
+ * @brief Set the orientation event threshold.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ * @param threshold The decimal place that should be considered a significant change.
+ *
+ * If threshold is 0.01, and any angle changes by 0.01 then a significant change
+ * has occured and the event callback will be invoked. If threshold is 1 then
+ * the angle has to change by a full degree to generate an event.
+ */
+void wiiuse_set_orient_threshold(struct wiimote_t* wm, float threshold) {
+ if (!wm) return;
+
+ wm->orient_threshold = threshold;
+}
+
+
+/**
+ * @brief Set the accelerometer event threshold.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ * @param threshold The decimal place that should be considered a significant change.
+ */
+void wiiuse_set_accel_threshold(struct wiimote_t* wm, int threshold) {
+ if (!wm) return;
+
+ wm->accel_threshold = threshold;
+}
+
+
+/**
+ * @brief Try to resync with the wiimote by starting a new handshake.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ */
+void wiiuse_resync(struct wiimote_t* wm) {
+ if (!wm) return;
+
+ wm->handshake_state = 0;
+ wiiuse_handshake(wm, NULL, 0);
+}
+
+
+/**
+ * @brief Set the normal and expansion handshake timeouts.
+ *
+ * @param wm Array of wiimote_t structures.
+ * @param wiimotes Number of objects in the wm array.
+ * @param normal_timeout The timeout in milliseconds for a normal read.
+ * @param exp_timeout The timeout in millisecondsd to wait for an expansion handshake.
+ */
+void wiiuse_set_timeout(struct wiimote_t** wm, int wiimotes, byte normal_timeout, byte exp_timeout) {
+ #ifdef WIN32
+ int i;
+
+ if (!wm) return;
+
+ for (i = 0; i < wiimotes; ++i) {
+ wm[i]->normal_timeout = normal_timeout;
+ wm[i]->exp_timeout = exp_timeout;
+ }
+ #endif
+}
diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse.h b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse.h new file mode 100644 index 0000000000..9dff81c8e4 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse.h @@ -0,0 +1,653 @@ +/*
+ * wiiuse
+ *
+ * Written By:
+ * Michael Laforest < para >
+ * Email: < thepara (--AT--) g m a i l [--DOT--] com >
+ *
+ * Copyright 2006-2007
+ *
+ * This file is part of wiiuse.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * $Header$
+ *
+ */
+
+/**
+ * @file
+ *
+ * @brief API header file.
+ *
+ * If this file is included from inside the wiiuse source
+ * and not from a third party program, then wiimote_internal.h
+ * is also included which extends this file.
+ */
+
+#ifndef WIIUSE_H_INCLUDED
+#define WIIUSE_H_INCLUDED
+
+#ifdef _WIN32
+ /* windows */
+ #include <windows.h>
+#else
+ /* nix */
+ #include <bluetooth/bluetooth.h>
+#endif
+
+#ifdef WIIUSE_INTERNAL_H_INCLUDED
+ #define WCONST
+#else
+ #define WCONST const
+#endif
+
+/* led bit masks */
+#define WIIMOTE_LED_NONE 0x00
+#define WIIMOTE_LED_1 0x10
+#define WIIMOTE_LED_2 0x20
+#define WIIMOTE_LED_3 0x40
+#define WIIMOTE_LED_4 0x80
+
+/* button codes */
+#define WIIMOTE_BUTTON_TWO 0x0001
+#define WIIMOTE_BUTTON_ONE 0x0002
+#define WIIMOTE_BUTTON_B 0x0004
+#define WIIMOTE_BUTTON_A 0x0008
+#define WIIMOTE_BUTTON_MINUS 0x0010
+#define WIIMOTE_BUTTON_ZACCEL_BIT6 0x0020
+#define WIIMOTE_BUTTON_ZACCEL_BIT7 0x0040
+#define WIIMOTE_BUTTON_HOME 0x0080
+#define WIIMOTE_BUTTON_LEFT 0x0100
+#define WIIMOTE_BUTTON_RIGHT 0x0200
+#define WIIMOTE_BUTTON_DOWN 0x0400
+#define WIIMOTE_BUTTON_UP 0x0800
+#define WIIMOTE_BUTTON_PLUS 0x1000
+#define WIIMOTE_BUTTON_ZACCEL_BIT4 0x2000
+#define WIIMOTE_BUTTON_ZACCEL_BIT5 0x4000
+#define WIIMOTE_BUTTON_UNKNOWN 0x8000
+#define WIIMOTE_BUTTON_ALL 0x1F9F
+
+/* nunchul button codes */
+#define NUNCHUK_BUTTON_Z 0x01
+#define NUNCHUK_BUTTON_C 0x02
+#define NUNCHUK_BUTTON_ALL 0x03
+
+/* classic controller button codes */
+#define CLASSIC_CTRL_BUTTON_UP 0x0001
+#define CLASSIC_CTRL_BUTTON_LEFT 0x0002
+#define CLASSIC_CTRL_BUTTON_ZR 0x0004
+#define CLASSIC_CTRL_BUTTON_X 0x0008
+#define CLASSIC_CTRL_BUTTON_A 0x0010
+#define CLASSIC_CTRL_BUTTON_Y 0x0020
+#define CLASSIC_CTRL_BUTTON_B 0x0040
+#define CLASSIC_CTRL_BUTTON_ZL 0x0080
+#define CLASSIC_CTRL_BUTTON_FULL_R 0x0200
+#define CLASSIC_CTRL_BUTTON_PLUS 0x0400
+#define CLASSIC_CTRL_BUTTON_HOME 0x0800
+#define CLASSIC_CTRL_BUTTON_MINUS 0x1000
+#define CLASSIC_CTRL_BUTTON_FULL_L 0x2000
+#define CLASSIC_CTRL_BUTTON_DOWN 0x4000
+#define CLASSIC_CTRL_BUTTON_RIGHT 0x8000
+#define CLASSIC_CTRL_BUTTON_ALL 0xFEFF
+
+/* guitar hero 3 button codes */
+#define GUITAR_HERO_3_BUTTON_STRUM_UP 0x0001
+#define GUITAR_HERO_3_BUTTON_YELLOW 0x0008
+#define GUITAR_HERO_3_BUTTON_GREEN 0x0010
+#define GUITAR_HERO_3_BUTTON_BLUE 0x0020
+#define GUITAR_HERO_3_BUTTON_RED 0x0040
+#define GUITAR_HERO_3_BUTTON_ORANGE 0x0080
+#define GUITAR_HERO_3_BUTTON_PLUS 0x0400
+#define GUITAR_HERO_3_BUTTON_MINUS 0x1000
+#define GUITAR_HERO_3_BUTTON_STRUM_DOWN 0x4000
+#define GUITAR_HERO_3_BUTTON_ALL 0xFEFF
+
+
+/* wiimote option flags */
+#define WIIUSE_SMOOTHING 0x01
+#define WIIUSE_CONTINUOUS 0x02
+#define WIIUSE_ORIENT_THRESH 0x04
+#define WIIUSE_INIT_FLAGS (WIIUSE_SMOOTHING | WIIUSE_ORIENT_THRESH)
+
+#define WIIUSE_ORIENT_PRECISION 100.0f
+
+/* expansion codes */
+#define EXP_NONE 0
+#define EXP_NUNCHUK 1
+#define EXP_CLASSIC 2
+#define EXP_GUITAR_HERO_3 3
+
+/* IR correction types */
+typedef enum ir_position_t {
+ WIIUSE_IR_ABOVE,
+ WIIUSE_IR_BELOW
+} ir_position_t;
+
+/**
+ * @brief Check if a button is pressed.
+ * @param dev Pointer to a wiimote_t or expansion structure.
+ * @param button The button you are interested in.
+ * @return 1 if the button is pressed, 0 if not.
+ */
+#define IS_PRESSED(dev, button) ((dev->btns & button) == button)
+
+/**
+ * @brief Check if a button is being held.
+ * @param dev Pointer to a wiimote_t or expansion structure.
+ * @param button The button you are interested in.
+ * @return 1 if the button is held, 0 if not.
+ */
+#define IS_HELD(dev, button) ((dev->btns_held & button) == button)
+
+/**
+ * @brief Check if a button is released on this event. \n\n
+ * This does not mean the button is not pressed, it means \n
+ * this button was just now released.
+ * @param dev Pointer to a wiimote_t or expansion structure.
+ * @param button The button you are interested in.
+ * @return 1 if the button is released, 0 if not.
+ *
+ */
+#define IS_RELEASED(dev, button) ((dev->btns_released & button) == button)
+
+/**
+ * @brief Check if a button has just been pressed this event.
+ * @param dev Pointer to a wiimote_t or expansion structure.
+ * @param button The button you are interested in.
+ * @return 1 if the button is pressed, 0 if not.
+ */
+#define IS_JUST_PRESSED(dev, button) (IS_PRESSED(dev, button) && !IS_HELD(dev, button)) + +/** + * @brief Return the IR sensitivity level. + * @param wm Pointer to a wiimote_t structure. + * @param lvl [out] Pointer to an int that will hold the level setting. + * If no level is set 'lvl' will be set to 0. + */ +#define WIIUSE_GET_IR_SENSITIVITY(dev, lvl) \ + do { \ + if ((wm->state & 0x0200) == 0x0200) *lvl = 1; \ + else if ((wm->state & 0x0400) == 0x0400) *lvl = 2; \ + else if ((wm->state & 0x0800) == 0x0800) *lvl = 3; \ + else if ((wm->state & 0x1000) == 0x1000) *lvl = 4; \ + else if ((wm->state & 0x2000) == 0x2000) *lvl = 5; \ + else *lvl = 0; \ + } while (0) +
+#define WIIUSE_USING_ACC(wm) ((wm->state & 0x020) == 0x020)
+#define WIIUSE_USING_EXP(wm) ((wm->state & 0x040) == 0x040)
+#define WIIUSE_USING_IR(wm) ((wm->state & 0x080) == 0x080)
+#define WIIUSE_USING_SPEAKER(wm) ((wm->state & 0x100) == 0x100)
+
+#define WIIUSE_IS_LED_SET(wm, num) ((wm->leds & WIIMOTE_LED_##num) == WIIMOTE_LED_##num)
+
+/*
+ * Largest known payload is 21 bytes.
+ * Add 2 for the prefix and round up to a power of 2.
+ */
+#define MAX_PAYLOAD 32
+
+/*
+ * This is left over from an old hack, but it may actually
+ * be a useful feature to keep so it wasn't removed.
+ */
+#ifdef WIN32
+ #define WIIMOTE_DEFAULT_TIMEOUT 10
+ #define WIIMOTE_EXP_TIMEOUT 10
+#endif
+
+typedef unsigned char byte;
+typedef char sbyte;
+
+struct wiimote_t;
+struct vec3b_t;
+struct orient_t;
+struct gforce_t;
+
+
+/**
+ * @brief Callback that handles a read event.
+ *
+ * @param wm Pointer to a wiimote_t structure.
+ * @param data Pointer to the filled data block.
+ * @param len Length in bytes of the data block.
+ *
+ * @see wiiuse_init()
+ *
+ * A registered function of this type is called automatically by the wiiuse
+ * library when the wiimote has returned the full data requested by a previous
+ * call to wiiuse_read_data().
+ */
+typedef void (*wiiuse_read_cb)(struct wiimote_t* wm, byte* data, unsigned short len);
+
+
+/**
+ * @struct read_req_t
+ * @brief Data read request structure.
+ */
+struct read_req_t {
+ wiiuse_read_cb cb; /**< read data callback */
+ byte* buf; /**< buffer where read data is written */
+ unsigned int addr; /**< the offset that the read started at */
+ unsigned short size; /**< the length of the data read */
+ unsigned short wait; /**< num bytes still needed to finish read */ + byte dirty; /**< set to 1 if not using callback and needs to be cleaned up */
+
+ struct read_req_t* next; /**< next read request in the queue */
+};
+
+
+/**
+ * @struct vec2b_t
+ * @brief Unsigned x,y byte vector.
+ */
+typedef struct vec2b_t {
+ byte x, y;
+} vec2b_t;
+
+
+/**
+ * @struct vec3b_t
+ * @brief Unsigned x,y,z byte vector.
+ */
+typedef struct vec3b_t {
+ byte x, y, z;
+} vec3b_t;
+
+
+/**
+ * @struct vec3f_t
+ * @brief Signed x,y,z float struct.
+ */
+typedef struct vec3f_t {
+ float x, y, z;
+} vec3f_t;
+
+
+/**
+ * @struct orient_t
+ * @brief Orientation struct.
+ *
+ * Yaw, pitch, and roll range from -180 to 180 degrees.
+ */
+typedef struct orient_t {
+ float roll; /**< roll, this may be smoothed if enabled */
+ float pitch; /**< pitch, this may be smoothed if enabled */
+ float yaw;
+
+ float a_roll; /**< absolute roll, unsmoothed */
+ float a_pitch; /**< absolute pitch, unsmoothed */
+} orient_t;
+
+
+/**
+ * @struct gforce_t
+ * @brief Gravity force struct.
+ */
+typedef struct gforce_t {
+ float x, y, z;
+} gforce_t;
+
+
+/**
+ * @struct accel_t
+ * @brief Accelerometer struct. For any device with an accelerometer.
+ */
+typedef struct accel_t {
+ struct vec3b_t cal_zero; /**< zero calibration */
+ struct vec3b_t cal_g; /**< 1g difference around 0cal */
+
+ float st_roll; /**< last smoothed roll value */
+ float st_pitch; /**< last smoothed roll pitch */
+ float st_alpha; /**< alpha value for smoothing [0-1] */
+} accel_t;
+
+
+/**
+ * @struct ir_dot_t
+ * @brief A single IR source.
+ */
+typedef struct ir_dot_t {
+ byte visible; /**< if the IR source is visible */
+
+ unsigned int x; /**< interpolated X coordinate */
+ unsigned int y; /**< interpolated Y coordinate */
+
+ short rx; /**< raw X coordinate (0-1023) */
+ short ry; /**< raw Y coordinate (0-767) */
+
+ byte order; /**< increasing order by x-axis value */
+
+ byte size; /**< size of the IR dot (0-15) */
+} ir_dot_t;
+
+
+/**
+ * @enum aspect_t
+ * @brief Screen aspect ratio.
+ */
+typedef enum aspect_t {
+ WIIUSE_ASPECT_4_3,
+ WIIUSE_ASPECT_16_9
+} aspect_t;
+
+
+/**
+ * @struct ir_t
+ * @brief IR struct. Hold all data related to the IR tracking.
+ */
+typedef struct ir_t {
+ struct ir_dot_t dot[4]; /**< IR dots */
+ byte num_dots; /**< number of dots at this time */
+
+ enum aspect_t aspect; /**< aspect ratio of the screen */
+
+ enum ir_position_t pos; /**< IR sensor bar position */
+
+ unsigned int vres[2]; /**< IR virtual screen resolution */
+ int offset[2]; /**< IR XY correction offset */
+ int state; /**< keeps track of the IR state */
+
+ int ax; /**< absolute X coordinate */
+ int ay; /**< absolute Y coordinate */
+
+ int x; /**< calculated X coordinate */
+ int y; /**< calculated Y coordinate */
+
+ float distance; /**< pixel distance between first 2 dots*/
+ float z; /**< calculated distance */
+} ir_t;
+
+
+/**
+ * @struct joystick_t
+ * @brief Joystick calibration structure.
+ *
+ * The angle \a ang is relative to the positive y-axis into quadrant I
+ * and ranges from 0 to 360 degrees. So if the joystick is held straight
+ * upwards then angle is 0 degrees. If it is held to the right it is 90,
+ * down is 180, and left is 270.
+ *
+ * The magnitude \a mag is the distance from the center to where the
+ * joystick is being held. The magnitude ranges from 0 to 1.
+ * If the joystick is only slightly tilted from the center the magnitude
+ * will be low, but if it is closer to the outter edge the value will
+ * be higher.
+ */
+typedef struct joystick_t {
+ struct vec2b_t max; /**< maximum joystick values */
+ struct vec2b_t min; /**< minimum joystick values */
+ struct vec2b_t center; /**< center joystick values */
+
+ float ang; /**< angle the joystick is being held */
+ float mag; /**< magnitude of the joystick (range 0-1) */
+} joystick_t;
+
+
+/**
+ * @struct nunchuk_t
+ * @brief Nunchuk expansion device.
+ */
+typedef struct nunchuk_t {
+ struct accel_t accel_calib; /**< nunchuk accelerometer calibration */
+ struct joystick_t js; /**< joystick calibration */
+
+ int* flags; /**< options flag (points to wiimote_t.flags) */
+
+ byte btns; /**< what buttons have just been pressed */
+ byte btns_held; /**< what buttons are being held down */
+ byte btns_released; /**< what buttons were just released this */
+ + float orient_threshold; /**< threshold for orient to generate an event */ + int accel_threshold; /**< threshold for accel to generate an event */ +
+ struct vec3b_t accel; /**< current raw acceleration data */
+ struct orient_t orient; /**< current orientation on each axis */
+ struct gforce_t gforce; /**< current gravity forces on each axis */
+} nunchuk_t;
+
+
+/**
+ * @struct classic_ctrl_t
+ * @brief Classic controller expansion device.
+ */
+typedef struct classic_ctrl_t {
+ short btns; /**< what buttons have just been pressed */
+ short btns_held; /**< what buttons are being held down */
+ short btns_released; /**< what buttons were just released this */
+
+ float r_shoulder; /**< right shoulder button (range 0-1) */
+ float l_shoulder; /**< left shoulder button (range 0-1) */
+
+ struct joystick_t ljs; /**< left joystick calibration */
+ struct joystick_t rjs; /**< right joystick calibration */
+} classic_ctrl_t;
+
+
+/**
+ * @struct guitar_hero_3_t
+ * @brief Guitar Hero 3 expansion device.
+ */
+typedef struct guitar_hero_3_t {
+ short btns; /**< what buttons have just been pressed */
+ short btns_held; /**< what buttons are being held down */
+ short btns_released; /**< what buttons were just released this */
+
+ float whammy_bar; /**< whammy bar (range 0-1) */
+
+ struct joystick_t js; /**< joystick calibration */
+} guitar_hero_3_t;
+
+
+/**
+ * @struct expansion_t
+ * @brief Generic expansion device plugged into wiimote.
+ */
+typedef struct expansion_t {
+ int type; /**< type of expansion attached */
+
+ union {
+ struct nunchuk_t nunchuk;
+ struct classic_ctrl_t classic;
+ struct guitar_hero_3_t gh3;
+ };
+} expansion_t;
+
+
+/**
+ * @enum win32_bt_stack_t
+ * @brief Available bluetooth stacks for Windows.
+ */
+typedef enum win_bt_stack_t {
+ WIIUSE_STACK_UNKNOWN,
+ WIIUSE_STACK_MS,
+ WIIUSE_STACK_BLUESOLEIL
+} win_bt_stack_t;
+
+
+/**
+ * @struct wiimote_state_t
+ * @brief Significant data from the previous event.
+ */
+typedef struct wiimote_state_t {
+ /* expansion_t */
+ float exp_ljs_ang;
+ float exp_rjs_ang;
+ float exp_ljs_mag;
+ float exp_rjs_mag;
+ unsigned short exp_btns;
+ struct orient_t exp_orient;
+ struct vec3b_t exp_accel; + float exp_r_shoulder;
+ float exp_l_shoulder;
+
+ /* ir_t */
+ int ir_ax;
+ int ir_ay;
+ float ir_distance;
+
+ struct orient_t orient;
+ unsigned short btns;
+
+ struct vec3b_t accel;
+} wiimote_state_t;
+
+
+/**
+ * @enum WIIUSE_EVENT_TYPE
+ * @brief Events that wiiuse can generate from a poll.
+ */
+typedef enum WIIUSE_EVENT_TYPE {
+ WIIUSE_NONE = 0,
+ WIIUSE_EVENT,
+ WIIUSE_STATUS, + WIIUSE_CONNECT,
+ WIIUSE_DISCONNECT, + WIIUSE_UNEXPECTED_DISCONNECT, + WIIUSE_READ_DATA, + WIIUSE_NUNCHUK_INSERTED,
+ WIIUSE_NUNCHUK_REMOVED, + WIIUSE_CLASSIC_CTRL_INSERTED, + WIIUSE_CLASSIC_CTRL_REMOVED, + WIIUSE_GUITAR_HERO_3_CTRL_INSERTED, + WIIUSE_GUITAR_HERO_3_CTRL_REMOVED +} WIIUSE_EVENT_TYPE;
+
+/**
+ * @struct wiimote_t
+ * @brief Wiimote structure.
+ */
+typedef struct wiimote_t {
+ WCONST int unid; /**< user specified id */
+
+ #ifndef WIN32
+ WCONST bdaddr_t bdaddr; /**< bt address */
+ WCONST char bdaddr_str[18]; /**< readable bt address */
+ WCONST int out_sock; /**< output socket */
+ WCONST int in_sock; /**< input socket */
+ #else
+ WCONST HANDLE dev_handle; /**< HID handle */
+ WCONST OVERLAPPED hid_overlap; /**< overlap handle */
+ WCONST enum win_bt_stack_t stack; /**< type of bluetooth stack to use */
+ WCONST int timeout; /**< read timeout */
+ WCONST byte normal_timeout; /**< normal timeout */
+ WCONST byte exp_timeout; /**< timeout for expansion handshake */
+ #endif
+
+ WCONST int state; /**< various state flags */
+ WCONST byte leds; /**< currently lit leds */
+ WCONST float battery_level; /**< battery level */
+
+ WCONST int flags; /**< options flag */ + + WCONST byte handshake_state; /**< the state of the connection handshake */
+
+ WCONST struct read_req_t* read_req; /**< list of data read requests */
+ WCONST struct accel_t accel_calib; /**< wiimote accelerometer calibration */
+ WCONST struct expansion_t exp; /**< wiimote expansion device */
+
+ WCONST struct vec3b_t accel; /**< current raw acceleration data */
+ WCONST struct orient_t orient; /**< current orientation on each axis */
+ WCONST struct gforce_t gforce; /**< current gravity forces on each axis */
+
+ WCONST struct ir_t ir; /**< IR data */
+
+ WCONST unsigned short btns; /**< what buttons have just been pressed */
+ WCONST unsigned short btns_held; /**< what buttons are being held down */
+ WCONST unsigned short btns_released; /**< what buttons were just released this */
+
+ WCONST float orient_threshold; /**< threshold for orient to generate an event */
+ WCONST int accel_threshold; /**< threshold for accel to generate an event */
+
+ WCONST struct wiimote_state_t lstate; /**< last saved state */
+
+ WCONST WIIUSE_EVENT_TYPE event; /**< type of event that occured */
+ WCONST byte event_buf[MAX_PAYLOAD]; /**< event buffer */
+} wiimote;
+
+
+/*****************************************
+ *
+ * Include API specific stuff
+ *
+ *****************************************/
+
+#ifdef _WIN32
+ #define WIIUSE_EXPORT_DECL __declspec(dllexport)
+ #define WIIUSE_IMPORT_DECL __declspec(dllimport)
+#else
+ #define WIIUSE_EXPORT_DECL
+ #define WIIUSE_IMPORT_DECL
+#endif
+
+#ifdef WIIUSE_COMPILE_LIB
+ #define WIIUSE_EXPORT WIIUSE_EXPORT_DECL
+#else
+ #define WIIUSE_EXPORT WIIUSE_IMPORT_DECL
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* wiiuse.c */
+WIIUSE_EXPORT extern const char* wiiuse_version();
+
+WIIUSE_EXPORT extern struct wiimote_t** wiiuse_init(int wiimotes);
+WIIUSE_EXPORT extern void wiiuse_disconnected(struct wiimote_t* wm);
+WIIUSE_EXPORT extern void wiiuse_cleanup(struct wiimote_t** wm, int wiimotes);
+WIIUSE_EXPORT extern void wiiuse_rumble(struct wiimote_t* wm, int status);
+WIIUSE_EXPORT extern void wiiuse_toggle_rumble(struct wiimote_t* wm);
+WIIUSE_EXPORT extern void wiiuse_set_leds(struct wiimote_t* wm, int leds);
+WIIUSE_EXPORT extern void wiiuse_motion_sensing(struct wiimote_t* wm, int status);
+WIIUSE_EXPORT extern int wiiuse_read_data(struct wiimote_t* wm, byte* buffer, unsigned int offset, unsigned short len);
+WIIUSE_EXPORT extern int wiiuse_write_data(struct wiimote_t* wm, unsigned int addr, byte* data, byte len);
+WIIUSE_EXPORT extern void wiiuse_status(struct wiimote_t* wm);
+WIIUSE_EXPORT extern struct wiimote_t* wiiuse_get_by_id(struct wiimote_t** wm, int wiimotes, int unid);
+WIIUSE_EXPORT extern int wiiuse_set_flags(struct wiimote_t* wm, int enable, int disable);
+WIIUSE_EXPORT extern float wiiuse_set_smooth_alpha(struct wiimote_t* wm, float alpha);
+WIIUSE_EXPORT extern void wiiuse_set_bluetooth_stack(struct wiimote_t** wm, int wiimotes, enum win_bt_stack_t type);
+WIIUSE_EXPORT extern void wiiuse_set_orient_threshold(struct wiimote_t* wm, float threshold);
+WIIUSE_EXPORT extern void wiiuse_resync(struct wiimote_t* wm);
+WIIUSE_EXPORT extern void wiiuse_set_timeout(struct wiimote_t** wm, int wiimotes, byte normal_timeout, byte exp_timeout);
+WIIUSE_EXPORT extern void wiiuse_set_accel_threshold(struct wiimote_t* wm, int threshold);
+
+/* connect.c */
+WIIUSE_EXPORT extern int wiiuse_find(struct wiimote_t** wm, int max_wiimotes, int timeout);
+WIIUSE_EXPORT extern int wiiuse_connect(struct wiimote_t** wm, int wiimotes);
+WIIUSE_EXPORT extern void wiiuse_disconnect(struct wiimote_t* wm);
+
+/* events.c */
+WIIUSE_EXPORT extern int wiiuse_poll(struct wiimote_t** wm, int wiimotes);
+
+/* ir.c */
+WIIUSE_EXPORT extern void wiiuse_set_ir(struct wiimote_t* wm, int status);
+WIIUSE_EXPORT extern void wiiuse_set_ir_vres(struct wiimote_t* wm, unsigned int x, unsigned int y);
+WIIUSE_EXPORT extern void wiiuse_set_ir_position(struct wiimote_t* wm, enum ir_position_t pos);
+WIIUSE_EXPORT extern void wiiuse_set_aspect_ratio(struct wiimote_t* wm, enum aspect_t aspect); +WIIUSE_EXPORT extern void wiiuse_set_ir_sensitivity(struct wiimote_t* wm, int level); + +/* nunchuk.c */ +WIIUSE_EXPORT extern void wiiuse_set_nunchuk_orient_threshold(struct wiimote_t* wm, float threshold); +WIIUSE_EXPORT extern void wiiuse_set_nunchuk_accel_threshold(struct wiimote_t* wm, int threshold); +
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* WIIUSE_H_INCLUDED */
+
diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse_internal.h b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse_internal.h new file mode 100644 index 0000000000..1b2f423862 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse_internal.h @@ -0,0 +1,226 @@ +/* + * wiiuse + * + * Written By: + * Michael Laforest < para > + * Email: < thepara (--AT--) g m a i l [--DOT--] com > + * + * Copyright 2006-2007 + * + * This file is part of wiiuse. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * $Header$ + * + */ + +/** + * @file + * @brief General internal wiiuse stuff. + * + * Since Wiiuse is a library, wiiuse.h is a duplicate + * of the API header. + * + * The code that would normally go in that file, but + * which is not needed by third party developers, + * is put here. + * + * So wiiuse_internal.h is included by other files + * internally, wiiuse.h is included only here. + */ + +#ifndef WIIUSE_INTERNAL_H_INCLUDED +#define WIIUSE_INTERNAL_H_INCLUDED + +#ifndef WIN32 + #include <arpa/inet.h> /* htons() */ + #include <bluetooth/bluetooth.h> +#endif + +#include "definitions.h" + +/* wiiuse version */ +#define WIIUSE_VERSION "0.12" + +/******************** + * + * Wiimote internal codes + * + ********************/ + +/* Communication channels */ +#define WM_OUTPUT_CHANNEL 0x11 +#define WM_INPUT_CHANNEL 0x13 + +#define WM_SET_REPORT 0x50 + +/* commands */ +#define WM_CMD_LED 0x11 +#define WM_CMD_REPORT_TYPE 0x12 +#define WM_CMD_RUMBLE 0x13 +#define WM_CMD_IR 0x13 +#define WM_CMD_CTRL_STATUS 0x15 +#define WM_CMD_WRITE_DATA 0x16 +#define WM_CMD_READ_DATA 0x17 +#define WM_CMD_IR_2 0x1A + +/* input report ids */ +#define WM_RPT_CTRL_STATUS 0x20 +#define WM_RPT_READ 0x21 +#define WM_RPT_WRITE 0x22 +#define WM_RPT_BTN 0x30 +#define WM_RPT_BTN_ACC 0x31 +#define WM_RPT_BTN_ACC_IR 0x33 +#define WM_RPT_BTN_EXP 0x34 +#define WM_RPT_BTN_ACC_EXP 0x35 +#define WM_RPT_BTN_IR_EXP 0x36 +#define WM_RPT_BTN_ACC_IR_EXP 0x37 + +#define WM_BT_INPUT 0x01 +#define WM_BT_OUTPUT 0x02 + +/* Identify the wiimote device by its class */ +#define WM_DEV_CLASS_0 0x04 +#define WM_DEV_CLASS_1 0x25 +#define WM_DEV_CLASS_2 0x00 +#define WM_VENDOR_ID 0x057E +#define WM_PRODUCT_ID 0x0306 + +/* controller status stuff */ +#define WM_MAX_BATTERY_CODE 0xC8 + +/* offsets in wiimote memory */ +#define WM_MEM_OFFSET_CALIBRATION 0x16 +#define WM_EXP_MEM_BASE 0x04A40000 +#define WM_EXP_MEM_ENABLE 0x04A40040 +#define WM_EXP_MEM_CALIBR 0x04A40020 + +#define WM_REG_IR 0x04B00030 +#define WM_REG_IR_BLOCK1 0x04B00000 +#define WM_REG_IR_BLOCK2 0x04B0001A +#define WM_REG_IR_MODENUM 0x04B00033 + +/* ir block data */ +#define WM_IR_BLOCK1_LEVEL1 "\x02\x00\x00\x71\x01\x00\x64\x00\xfe" +#define WM_IR_BLOCK2_LEVEL1 "\xfd\x05" +#define WM_IR_BLOCK1_LEVEL2 "\x02\x00\x00\x71\x01\x00\x96\x00\xb4" +#define WM_IR_BLOCK2_LEVEL2 "\xb3\x04" +#define WM_IR_BLOCK1_LEVEL3 "\x02\x00\x00\x71\x01\x00\xaa\x00\x64" +#define WM_IR_BLOCK2_LEVEL3 "\x63\x03" +#define WM_IR_BLOCK1_LEVEL4 "\x02\x00\x00\x71\x01\x00\xc8\x00\x36" +#define WM_IR_BLOCK2_LEVEL4 "\x35\x03" +#define WM_IR_BLOCK1_LEVEL5 "\x07\x00\x00\x71\x01\x00\x72\x00\x20" +#define WM_IR_BLOCK2_LEVEL5 "\x1f\x03" + +#define WM_IR_TYPE_BASIC 0x01 +#define WM_IR_TYPE_EXTENDED 0x03 + +/* controller status flags for the first message byte */ +/* bit 1 is unknown */ +#define WM_CTRL_STATUS_BYTE1_ATTACHMENT 0x02 +#define WM_CTRL_STATUS_BYTE1_SPEAKER_ENABLED 0x04 +#define WM_CTRL_STATUS_BYTE1_IR_ENABLED 0x08 +#define WM_CTRL_STATUS_BYTE1_LED_1 0x10 +#define WM_CTRL_STATUS_BYTE1_LED_2 0x20 +#define WM_CTRL_STATUS_BYTE1_LED_3 0x40 +#define WM_CTRL_STATUS_BYTE1_LED_4 0x80 + +/* aspect ratio */ +#define WM_ASPECT_16_9_X 660 +#define WM_ASPECT_16_9_Y 370 +#define WM_ASPECT_4_3_X 560 +#define WM_ASPECT_4_3_Y 420 + + +/** + * Expansion stuff + */ + +/* encrypted expansion id codes (located at 0x04A400FC) */ +#define EXP_ID_CODE_NUNCHUK 0x9A1EFEFE +#define EXP_ID_CODE_CLASSIC_CONTROLLER 0x9A1EFDFD +#define EXP_ID_CODE_GUITAR 0x9A1EFDFB + +#define EXP_HANDSHAKE_LEN 224
+
+/******************** + * + * End Wiimote internal codes + * + ********************/ + +/* wiimote state flags - (some duplicated in wiiuse.h)*/ +#define WIIMOTE_STATE_DEV_FOUND 0x0001 +#define WIIMOTE_STATE_HANDSHAKE 0x0002 /* actual connection exists but no handshake yet */ +#define WIIMOTE_STATE_HANDSHAKE_COMPLETE 0x0004 /* actual connection exists but no handshake yet */ +#define WIIMOTE_STATE_CONNECTED 0x0008 +#define WIIMOTE_STATE_RUMBLE 0x0010 +#define WIIMOTE_STATE_ACC 0x0020 +#define WIIMOTE_STATE_EXP 0x0040 +#define WIIMOTE_STATE_IR 0x0080 +#define WIIMOTE_STATE_SPEAKER 0x0100 +#define WIIMOTE_STATE_IR_SENS_LVL1 0x0200 +#define WIIMOTE_STATE_IR_SENS_LVL2 0x0400 +#define WIIMOTE_STATE_IR_SENS_LVL3 0x0800 +#define WIIMOTE_STATE_IR_SENS_LVL4 0x1000 +#define WIIMOTE_STATE_IR_SENS_LVL5 0x2000 + +#define WIIMOTE_INIT_STATES (WIIMOTE_STATE_IR_SENS_LVL3) + +/* macro to manage states */ +#define WIIMOTE_IS_SET(wm, s) ((wm->state & (s)) == (s)) +#define WIIMOTE_ENABLE_STATE(wm, s) (wm->state |= (s)) +#define WIIMOTE_DISABLE_STATE(wm, s) (wm->state &= ~(s)) +#define WIIMOTE_TOGGLE_STATE(wm, s) ((wm->state & (s)) ? WIIMOTE_DISABLE_STATE(wm, s) : WIIMOTE_ENABLE_STATE(wm, s)) + +#define WIIMOTE_IS_FLAG_SET(wm, s) ((wm->flags & (s)) == (s)) +#define WIIMOTE_ENABLE_FLAG(wm, s) (wm->flags |= (s)) +#define WIIMOTE_DISABLE_FLAG(wm, s) (wm->flags &= ~(s)) +#define WIIMOTE_TOGGLE_FLAG(wm, s) ((wm->flags & (s)) ? WIIMOTE_DISABLE_FLAG(wm, s) : WIIMOTE_ENABLE_FLAG(wm, s)) + +#define NUNCHUK_IS_FLAG_SET(wm, s) ((*(wm->flags) & (s)) == (s)) + +/* misc macros */ +#define WIIMOTE_ID(wm) (wm->unid) +#define WIIMOTE_IS_CONNECTED(wm) (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_CONNECTED)) + +/* + * Smooth tilt calculations are computed with the + * exponential moving average formula: + * St = St_last + (alpha * (tilt - St_last)) + * alpha is between 0 and 1 + */ +#define WIIUSE_DEFAULT_SMOOTH_ALPHA 0.07f + +#define SMOOTH_ROLL 0x01 +#define SMOOTH_PITCH 0x02
+
+#include "wiiuse.h" +
+#ifdef __cplusplus +extern "C" { +#endif + +/* not part of the api */ +int wiiuse_set_report_type(struct wiimote_t* wm); +void wiiuse_send_next_pending_read_request(struct wiimote_t* wm); +int wiiuse_send(struct wiimote_t* wm, byte report_type, byte* msg, int len); +int wiiuse_read_data_cb(struct wiimote_t* wm, wiiuse_read_cb read_cb, byte* buffer, unsigned int offset, unsigned short len); + +#ifdef __cplusplus +} +#endif + +#endif /* WIIUSE_INTERNAL_H_INCLUDED */ diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/wiiuse.cbp b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/wiiuse.cbp new file mode 100644 index 0000000000..a71d3a0ceb --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/wiiuse.cbp @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<CodeBlocks_project_file> + <FileVersion major="1" minor="6" /> + <Project> + <Option title="wiiuse" /> + <Option makefile_is_custom="1" /> + <Option pch_mode="2" /> + <Option compiler="gcc" /> + <MakeCommands> + <Build command="$make $target" /> + <CompileFile command="$make $file" /> + <Clean command="$make clean" /> + <DistClean command="$make distclean" /> + </MakeCommands> + <Build> + <Target title="Release"> + <Option output="bin/Release/wiiuse" prefix_auto="1" extension_auto="1" /> + <Option object_output="obj/Release/" /> + <Option type="1" /> + <Option compiler="gcc" /> + <Compiler> + <Add option="-O2" /> + </Compiler> + <Linker> + <Add option="-s" /> + </Linker> + </Target> + </Build> + <Compiler> + <Add option="-Wall" /> + </Compiler> + <Unit filename="CHANGELOG" /> + <Unit filename="README" /> + <Unit filename="example-sdl/sdl.c"> + <Option compilerVar="CC" /> + </Unit> + <Unit filename="example/example.c"> + <Option compilerVar="CC" /> + </Unit> + <Unit filename="src/classic.c"> + <Option compilerVar="CC" /> + </Unit> + <Unit filename="src/classic.h" /> + <Unit filename="src/definitions.h" /> + <Unit filename="src/dynamics.c"> + <Option compilerVar="CC" /> + </Unit> + <Unit filename="src/dynamics.h" /> + <Unit filename="src/events.c"> + <Option compilerVar="CC" /> + </Unit> + <Unit filename="src/events.h" /> + <Unit filename="src/guitar_hero_3.c"> + <Option compilerVar="CC" /> + </Unit> + <Unit filename="src/guitar_hero_3.h" /> + <Unit filename="src/io.c"> + <Option compilerVar="CC" /> + </Unit> + <Unit filename="src/io.h" /> + <Unit filename="src/io_nix.c"> + <Option compilerVar="CC" /> + </Unit> + <Unit filename="src/io_win.c"> + <Option compilerVar="CC" /> + </Unit> + <Unit filename="src/ir.c"> + <Option compilerVar="CC" /> + </Unit> + <Unit filename="src/ir.h" /> + <Unit filename="src/nunchuk.c"> + <Option compilerVar="CC" /> + </Unit> + <Unit filename="src/nunchuk.h" /> + <Unit filename="src/os.h" /> + <Unit filename="src/wiiuse.c"> + <Option compilerVar="CC" /> + </Unit> + <Unit filename="src/wiiuse.h" /> + <Unit filename="src/wiiuse_internal.h" /> + <Extensions> + <code_completion /> + <debugger /> + </Extensions> + </Project> +</CodeBlocks_project_file> diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/wiiuse.layout b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/wiiuse.layout new file mode 100644 index 0000000000..5b74afea49 --- /dev/null +++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/wiiuse.layout @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<CodeBlocks_layout_file> + <ActiveTarget name="Release" /> + <File name="CHANGELOG" open="1" top="0" tabpos="7"> + <Cursor position="0" topLine="0" /> + </File> + <File name="README" open="0" top="0" tabpos="8"> + <Cursor position="3582" topLine="0" /> + </File> + <File name="example-sdl/sdl.c" open="1" top="0" tabpos="6"> + <Cursor position="3235" topLine="105" /> + </File> + <File name="example/example.c" open="1" top="0" tabpos="5"> + <Cursor position="0" topLine="0" /> + </File> + <File name="src/classic.c" open="0" top="0" tabpos="4"> + <Cursor position="2464" topLine="53" /> + </File> + <File name="src/definitions.h" open="1" top="0" tabpos="9"> + <Cursor position="1099" topLine="12" /> + </File> + <File name="src/dynamics.c" open="0" top="0" tabpos="10"> + <Cursor position="3216" topLine="59" /> + </File> + <File name="src/events.c" open="1" top="0" tabpos="4"> + <Cursor position="2983" topLine="117" /> + </File> + <File name="src/guitar_hero_3.c" open="0" top="0" tabpos="0"> + <Cursor position="2604" topLine="45" /> + </File> + <File name="src/io.c" open="0" top="0" tabpos="6"> + <Cursor position="1817" topLine="60" /> + </File> + <File name="src/io_nix.c" open="0" top="0" tabpos="8"> + <Cursor position="6286" topLine="211" /> + </File> + <File name="src/io_win.c" open="0" top="0" tabpos="9"> + <Cursor position="4021" topLine="123" /> + </File> + <File name="src/ir.c" open="1" top="0" tabpos="8"> + <Cursor position="0" topLine="0" /> + </File> + <File name="src/ir.h" open="0" top="0" tabpos="0"> + <Cursor position="1059" topLine="0" /> + </File> + <File name="src/nunchuk.c" open="0" top="0" tabpos="10"> + <Cursor position="2454" topLine="43" /> + </File> + <File name="src/nunchuk.h" open="0" top="0" tabpos="0"> + <Cursor position="1171" topLine="0" /> + </File> + <File name="src/wiiuse.c" open="1" top="0" tabpos="1"> + <Cursor position="0" topLine="0" /> + </File> + <File name="src/wiiuse.h" open="1" top="1" tabpos="2"> + <Cursor position="0" topLine="0" /> + </File> + <File name="src/wiiuse_internal.h" open="1" top="0" tabpos="3"> + <Cursor position="3424" topLine="81" /> + </File> +</CodeBlocks_layout_file> diff --git a/tools/EventClients/Clients/XBMC Send/xbmc-send.py b/tools/EventClients/Clients/XBMC Send/xbmc-send.py new file mode 100644 index 0000000000..2746ae026a --- /dev/null +++ b/tools/EventClients/Clients/XBMC Send/xbmc-send.py @@ -0,0 +1,61 @@ +#!/usr/bin/python + +import sys +import getopt +from socket import * +try: + from xbmc.xbmcclient import * +except: + sys.path.append('../../lib/python') + from xbmcclient import * + +def usage(): + print "xbmc-send [OPTION] --action=ACTION" + print 'Example' + print '\txbmc-send --host=192.168.0.1 --port=9777 --action="XBMC.Quit"' + print "Options" + print "\t-?, --help\t\t\tWill bring up this message" + print "\t--host=HOST\t\t\tChoose what HOST to connect to (default=localhost)" + print "\t--port=PORT\t\t\tChoose what PORT to connect to (default=9777)" + print '\t--action=ACTION\t\t\tSends an action to XBMC, this option can be added multiple times to create a macro' + pass + +def main(): + try: + opts, args = getopt.getopt(sys.argv[1:], "?pa:v", ["help", "host=", "port=", "action="]) + except getopt.GetoptError, err: + # print help information and exit: + print str(err) # will print something like "option -a not recognized" + usage() + sys.exit(2) + ip = "localhost" + port = 9777 + actions = [] + verbose = False + for o, a in opts: + if o in ("-?", "--help"): + usage() + sys.exit() + elif o == "--host": + ip = a + elif o == "--port": + port = int(a) + elif o in ("-a", "--action"): + actions.append(a) + else: + assert False, "unhandled option" + + addr = (ip, port) + sock = socket(AF_INET,SOCK_DGRAM) + + if len(actions) is 0: + usage() + sys.exit(0) + + for action in actions: + print 'Sending action:', action + packet = PacketACTION(actionmessage=action, actiontype=ACTION_BUTTON) + packet.send(sock, addr) + +if __name__=="__main__": + main() diff --git a/tools/EventClients/Clients/Xbox360 Controller/Xbox360Controller.cpp b/tools/EventClients/Clients/Xbox360 Controller/Xbox360Controller.cpp new file mode 100644 index 0000000000..149026776e --- /dev/null +++ b/tools/EventClients/Clients/Xbox360 Controller/Xbox360Controller.cpp @@ -0,0 +1,154 @@ +#include "StdAfx.h"
+#include "Xbox360Controller.h"
+
+
+Xbox360Controller::Xbox360Controller(int num)
+{
+ this->num = num;
+ for (int i = 0; i < 14; i++)
+ {
+ button_down[i] = false;
+ button_released[i] = false;
+ button_pressed[i] = false;
+ }
+}
+
+XINPUT_STATE Xbox360Controller::getState()
+{
+ // Zeroise the state
+ ZeroMemory(&state, sizeof(XINPUT_STATE));
+
+ // Get the state
+ XInputGetState(num, &state);
+
+ return state;
+}
+
+void Xbox360Controller::updateButton(int num, int button)
+{
+ if (state.Gamepad.wButtons & button)
+ {
+ if (!button_down[num])
+ {
+ button_pressed[num] = true;
+ }
+ button_down[num] = true;
+ } else
+ {
+ if (button_down[num])
+ {
+ button_released[num] = true;
+ }
+ button_down[num] = false;
+ }
+}
+
+bool Xbox360Controller::buttonPressed(int num)
+{
+ return button_pressed[num];
+}
+
+bool Xbox360Controller::buttonReleased(int num)
+{
+ return button_released[num];
+}
+
+void Xbox360Controller::updateState()
+{
+ for (int i = 0; i < 14; i++)
+ {
+ button_released[i] = false;
+ button_pressed[i] = false;
+ }
+ if (isConnected())
+ {
+ XINPUT_STATE s = getState();
+ updateButton(0, XINPUT_GAMEPAD_A);
+ updateButton(1, XINPUT_GAMEPAD_B);
+ updateButton(2, XINPUT_GAMEPAD_X);
+ updateButton(3, XINPUT_GAMEPAD_Y);
+ updateButton(4, XINPUT_GAMEPAD_DPAD_UP);
+ updateButton(5, XINPUT_GAMEPAD_DPAD_DOWN);
+ updateButton(6, XINPUT_GAMEPAD_DPAD_LEFT);
+ updateButton(7, XINPUT_GAMEPAD_DPAD_RIGHT);
+ updateButton(8, XINPUT_GAMEPAD_START);
+ updateButton(9, XINPUT_GAMEPAD_BACK);
+ updateButton(10, XINPUT_GAMEPAD_LEFT_THUMB);
+ updateButton(11, XINPUT_GAMEPAD_RIGHT_THUMB);
+ updateButton(12, XINPUT_GAMEPAD_LEFT_SHOULDER);
+ updateButton(13, XINPUT_GAMEPAD_RIGHT_SHOULDER);
+ }
+}
+
+bool Xbox360Controller::isConnected()
+{
+ // Zeroise the state
+ ZeroMemory(&state, sizeof(XINPUT_STATE));
+
+ // Get the state
+ DWORD Result = XInputGetState(num, &state);
+
+ if(Result == ERROR_SUCCESS)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+bool Xbox360Controller::triggerMoved(int num)
+{
+ if (num == 0)
+ return (state.Gamepad.bRightTrigger &&
+ state.Gamepad.bRightTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD);
+ return (state.Gamepad.bLeftTrigger &&
+ state.Gamepad.bLeftTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD);
+}
+
+BYTE Xbox360Controller::getTrigger(int num)
+{
+ if (num == 0)
+ return state.Gamepad.bRightTrigger;
+ return state.Gamepad.bLeftTrigger;
+}
+
+bool Xbox360Controller::thumbMoved(int num)
+{
+ switch(num)
+ {
+ case 0:
+ return !(state.Gamepad.sThumbLX < XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE &&
+ state.Gamepad.sThumbLX > -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
+ case 1:
+ return !(state.Gamepad.sThumbLY < XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE &&
+ state.Gamepad.sThumbLY > -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
+ case 2:
+ return !(state.Gamepad.sThumbRX < XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE &&
+ state.Gamepad.sThumbRX > -XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
+ case 3:
+ return !(state.Gamepad.sThumbRY < XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE &&
+ state.Gamepad.sThumbRY > -XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
+ }
+
+ return false;
+}
+SHORT Xbox360Controller::getThumb(int num)
+{
+ switch (num)
+ {
+ case 0:
+ return state.Gamepad.sThumbLX;
+ case 1:
+ return state.Gamepad.sThumbLY;
+ case 2:
+ return state.Gamepad.sThumbRX;
+ case 3:
+ return state.Gamepad.sThumbRY;
+ }
+
+ return 0;
+}
+
diff --git a/tools/EventClients/Clients/Xbox360 Controller/Xbox360Controller.h b/tools/EventClients/Clients/Xbox360 Controller/Xbox360Controller.h new file mode 100644 index 0000000000..5a858d5f80 --- /dev/null +++ b/tools/EventClients/Clients/Xbox360 Controller/Xbox360Controller.h @@ -0,0 +1,28 @@ +#pragma once
+#include <windows.h>
+#include <XInput.h> // Defines XBOX controller API
+#pragma comment(lib, "XInput.lib") // Library containing necessary 360
+ // functions
+
+class Xbox360Controller
+{
+private:
+ XINPUT_STATE state;
+ int num;
+ bool button_down[14];
+ bool button_pressed[14];
+ bool button_released[14];
+
+ XINPUT_STATE getState();
+ void updateButton(int num, int button);
+public:
+ Xbox360Controller(int num);
+ void updateState();
+ bool isConnected();
+ bool buttonPressed(int num);
+ bool buttonReleased(int num);
+ bool thumbMoved(int num);
+ SHORT getThumb(int num);
+ bool triggerMoved(int num);
+ BYTE getTrigger(int num);
+};
diff --git a/tools/EventClients/Clients/Xbox360 Controller/Xbox360EventClient.cpp b/tools/EventClients/Clients/Xbox360 Controller/Xbox360EventClient.cpp new file mode 100644 index 0000000000..74b5e6a680 --- /dev/null +++ b/tools/EventClients/Clients/Xbox360 Controller/Xbox360EventClient.cpp @@ -0,0 +1,123 @@ +// Xbox360EventClient.cpp : Defines the entry point for the console application.
+//
+
+#include "stdafx.h"
+#include "Xbox360Controller.h"
+#include "../../lib/c++/xbmcclient.h"
+#pragma comment(lib, "wsock32.lib") // needed for xmbclient.h?
+
+// global variable :(
+// needed for exit event handler
+CXBMCClient *client;
+
+BOOL exitHandler( DWORD ctrlType )
+{
+ // TODO: Send BYE Packet
+ delete client;
+
+ WSACleanup();
+
+ return FALSE;
+}
+
+void checkTrigger(Xbox360Controller &cont, CXBMCClient *client, int num, const char* name)
+{
+ if (cont.triggerMoved(num))
+ {
+ client->SendButton(name, "XG", 0x20, cont.getTrigger(num) * 128);
+ }
+}
+
+void checkThumb(Xbox360Controller &cont, CXBMCClient *client, int num,
+ const char* leftname, const char* rightname)
+{
+ if (cont.thumbMoved(num))
+ {
+ if (cont.getThumb(num) < 0)
+ {
+ client->SendButton(leftname, "XG", 0x20, -cont.getThumb(num));
+ }
+ else
+ {
+ client->SendButton(rightname, "XG", 0x20, cont.getThumb(num));
+ }
+ }
+}
+
+void checkButton(Xbox360Controller &cont, CXBMCClient *client, int num, const char* name)
+{
+ if (cont.buttonPressed(num))
+ {
+ client->SendButton(name, "XG", 0x02);
+ }
+ else if (cont.buttonReleased(num))
+ {
+ client->SendButton(name, "XG", 0x04);
+ }
+}
+
+int main(int argc, char* argv[])
+{
+ char *host = "localhost";
+ char *port = "9777";
+
+ Xbox360Controller cont(0);
+
+ // Start Winsock stuff
+ WSADATA wsaData;
+ WSAStartup(MAKEWORD(2, 2), &wsaData);
+
+ if ( argc > 3 )
+ {
+ printf("USAGE: %s [HOST [PORT]]\n\nThe event client connects to the XBMC EventServer at HOST:PORT.\
+ Default value for HOST is localhost, default value for port is 9777.\n", argv[0]);
+ return -1;
+ }
+
+ if ( argc > 1 )
+ {
+ host = argv[1];
+ }
+
+ if ( argc > 2 )
+ {
+ port = argv[2];
+ }
+
+ client = new CXBMCClient(host, atoi(port));
+
+ SetConsoleCtrlHandler( (PHANDLER_ROUTINE) exitHandler, TRUE);
+
+ client->SendHELO("Xbox 360 Controller", 0);
+
+ while(true)
+ {
+ if (cont.isConnected())
+ {
+ cont.updateState();
+ checkButton(cont, client, 0, "a");
+ checkButton(cont, client, 1, "b");
+ checkButton(cont, client, 2, "x");
+ checkButton(cont, client, 3, "y");
+ checkButton(cont, client, 4, "dpadup");
+ checkButton(cont, client, 5, "dpaddown");
+ checkButton(cont, client, 6, "dpadleft");
+ checkButton(cont, client, 7, "dpadright");
+ checkButton(cont, client, 8, "start");
+ checkButton(cont, client, 9, "back");
+ checkButton(cont, client, 10, "leftthumbbutton");
+ checkButton(cont, client, 11, "rightthumbbutton");
+ checkButton(cont, client, 12, "white");
+ checkButton(cont, client, 13, "black");
+ checkTrigger(cont, client, 0, "rightanalogtrigger");
+ checkTrigger(cont, client, 1, "leftanalogtrigger");
+ checkThumb(cont, client, 0, "leftthumbstickleft", "leftthumbstickright");
+ checkThumb(cont, client, 1, "leftthumbstickdown", "leftthumbstickup");
+ checkThumb(cont, client, 2, "rightthumbstickleft", "rightthumbstickright");
+ checkThumb(cont, client, 3, "rightthumbstickdown", "rightthumbstickup");
+ }
+ Sleep(10);
+ }
+
+ return 0;
+}
diff --git a/tools/EventClients/Clients/Xbox360 Controller/Xbox360EventClient.vcproj b/tools/EventClients/Clients/Xbox360 Controller/Xbox360EventClient.vcproj new file mode 100644 index 0000000000..4e6f8c558e --- /dev/null +++ b/tools/EventClients/Clients/Xbox360 Controller/Xbox360EventClient.vcproj @@ -0,0 +1,208 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9,00"
+ Name="Xbox360EventClient"
+ ProjectGUID="{19E7A234-4F29-4828-BBC7-E3564AB57EF0}"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="0"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="Debug"
+ IntermediateDirectory="Debug"
+ ConfigurationType="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="true"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ LinkIncremental="2"
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="Release"
+ IntermediateDirectory="Release"
+ ConfigurationType="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;"
+ RuntimeLibrary="2"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="true"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ LinkIncremental="2"
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath=".\stdafx.h"
+ >
+ </File>
+ <File
+ RelativePath=".\targetver.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Xbox360Controller.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Resource Files"
+ Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx"
+ UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
+ >
+ </Filter>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+ >
+ <File
+ RelativePath=".\stdafx.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\Xbox360Controller.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\Xbox360EventClient.cpp"
+ >
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/tools/EventClients/Clients/Xbox360 Controller/stdafx.cpp b/tools/EventClients/Clients/Xbox360 Controller/stdafx.cpp new file mode 100644 index 0000000000..81d9f220d5 --- /dev/null +++ b/tools/EventClients/Clients/Xbox360 Controller/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes
+// Xbox360EventClient.pch will be the pre-compiled header
+// stdafx.obj will contain the pre-compiled type information
+
+#include "stdafx.h"
+
+// TODO: reference any additional headers you need in STDAFX.H
+// and not in this file
diff --git a/tools/EventClients/Clients/Xbox360 Controller/stdafx.h b/tools/EventClients/Clients/Xbox360 Controller/stdafx.h new file mode 100644 index 0000000000..47a0d0252b --- /dev/null +++ b/tools/EventClients/Clients/Xbox360 Controller/stdafx.h @@ -0,0 +1,15 @@ +// stdafx.h : include file for standard system include files,
+// or project specific include files that are used frequently, but
+// are changed infrequently
+//
+
+#pragma once
+
+#include "targetver.h"
+
+#include <stdio.h>
+#include <tchar.h>
+
+
+
+// TODO: reference additional headers your program requires here
diff --git a/tools/EventClients/Clients/Xbox360 Controller/targetver.h b/tools/EventClients/Clients/Xbox360 Controller/targetver.h new file mode 100644 index 0000000000..a38195a4ef --- /dev/null +++ b/tools/EventClients/Clients/Xbox360 Controller/targetver.h @@ -0,0 +1,13 @@ +#pragma once
+
+// The following macros define the minimum required platform. The minimum required platform
+// is the earliest version of Windows, Internet Explorer etc. that has the necessary features to run
+// your application. The macros work by enabling all features available on platform versions up to and
+// including the version specified.
+
+// Modify the following defines if you have to target a platform prior to the ones specified below.
+// Refer to MSDN for the latest info on corresponding values for different platforms.
+#ifndef _WIN32_WINNT // Specifies that the minimum required platform is Windows Vista.
+#define _WIN32_WINNT 0x0600 // Change this to the appropriate value to target other versions of Windows.
+#endif
+
diff --git a/tools/EventClients/Makefile b/tools/EventClients/Makefile new file mode 100644 index 0000000000..5e902f7fbc --- /dev/null +++ b/tools/EventClients/Makefile @@ -0,0 +1,32 @@ +# Default prefix dir +prefix=/usr/local +installdir=$(prefix) +WII_EXTRA_OPTS= + +all: wiimote install + +wiimote: + cd Clients/WiiRemote && \ + g++ CWIID_WiiRemote.cpp -lcwiid -DICON_PATH="\"$(installdir)/share/pixmaps/xbmc/\"" $(WII_EXTRA_OPTS) -o WiiRemote + +j2me-remote: + cd Clients/J2ME\ Client + ant -f build.xml + +install: + mkdir -p $(prefix)/bin + cp -a Clients/WiiRemote/WiiRemote $(prefix)/bin/xbmc-wiiremote + cp -a Clients/J2ME\ Client/j2me_remote.py $(prefix)/bin/xbmc-j2meremote + cp -a Clients/PS3\ BD\ Remote/ps3_remote.py $(prefix)/bin/xbmc-ps3remote + #cp -a Clients/PS3\ Sixaxis\ Controller/ps3d.py $(prefix)/bin/xbmc-ps3d + mkdir -p $(prefix)/lib/python2.5/site-packages/xbmc + echo 'ICON_PATH="$(installdir)/share/pixmaps/xbmc/"' > $(prefix)/lib/python2.5/site-packages/xbmc/defs.py + cp -a lib/python/* $(prefix)/lib/python2.5/site-packages/xbmc/ + cp -a Clients/PS3\ BD\ Remote/ps3_remote.py $(prefix)/lib/python2.5/site-packages/xbmc/ + mkdir -p $(prefix)/include/xbmc + cp -a lib/c++/* $(prefix)/include/xbmc/ + mkdir -p $(prefix)/share/pixmaps/xbmc + cp -a icons/* $(prefix)/share/pixmaps/xbmc/ + +clean: + rm -f Clients/WiiRemote/WiiRemote diff --git a/tools/EventClients/README.txt b/tools/EventClients/README.txt new file mode 100644 index 0000000000..6e1a454e5b --- /dev/null +++ b/tools/EventClients/README.txt @@ -0,0 +1,101 @@ +Event Client Examples and PS3 Sixaxis and Blu-Ray Remote Support +---------------------------------------------------------------- + +This directory contains 6 sample programs that demonstrate XBMC's +event server (which is still in development). The programs are in +Python and C++. XBMC also needs to be running (obviously) so that it +can receive the events. + +- example_button1.py | example_button1.cpp +- example_button2.py | example_button2.cpp +- example_notification.py | example_notification.cpp + +The first 2 show how button / key presses can be sent to XBMC. +The third one shows how to display notifications in XBMC. + +- xbmcclient.py +- xbmcclient.h + +These are the Python module and C++ header that you can use when +writing your own programs. It is not yet complete and likely to change +but is still usable. + +Implementation details can be found in the comments in the sample +programs. + + +PS3 Controller and PS3 Blu-Ray Remote Support +--------------------------------------------- + +There is also initial support for the PS3 controller (sixaxis) and the +PS3 Blu-Ray remote. + +Pairing of the PS3 Blu-Ray Remote +--------------------------------- + +The remote needs to be paired initially with the 'ps3_remote.py' +program in this directory which you can continue using if you do not +want to run 'ps3d.py' as root. The disadvantage of using +'ps3_remote.py' is that pairing is required on every run. Once initial +pairing is done, 'ps3d.py', when run as root, will automatically +detect incoming connections from both the PS3 remote and the Sixaxis +controller. + +Pairing of the PS3 Sixaxis Controller (TODO) +-------------------------------------------- + +The pairing of the PS3 controller is not yet handled automatically. It +can however be done using the program "sixaxis.c" available from: + +http://www.pabr.org/sixlinux/sixlinux.en.html + +Once pairing for eiher or both has been done, run the ps3d.py program +as root after disabling any existing HID servers that might currently +be running. The program requires root prvilieges since it listens on +Bluetooth L2CAP PSMs 17 and 19. + +Using the PS3 Sixaxis Controller +-------------------------------- + +Currently, all that is supported with the Sixaxis controller is to be able +emulate the mouse behavior. Hold down the PS button and wave the controller +around and watch the mouse in XBMC mouse. Tilt it from left to right (along +your Z axis) to control horizontal motion. Tilt it towards and away from you +along (along your X axis) to control vertical mouse movement. + +That's all for now. + +WiiRemote Support +----------------- + +The executable depends on libcwiid and libbluetooth and is compiled using +# g++ WiiRemote.cpp -lcwiid -o WiiRemote +The WiiRemote will emulate mouse by default but can be disabled by running with --disable-mouseemulation +The sensitivity of the mouseemulation can be set using the --deadzone_x or --deadzone_y where the number is +the percentage of the space is considered "dead", higher means more sensative. +Other commands can be listed with --help + +The WiiRemote is mappable with keymap.xml where button id's are the following: +1 = Up +2 = Down +3 = Left +4 = Right +5 = A +6 = B +7 = Minus +8 = Home +9 = Plus +10 = 1 +11 = 2 +The name is by standard WiiRemote but this can be changed with the --joystick-name + +J2ME (Java Phone Application) +----------------------------- + +To use the J2ME client only CLDC 1.0 and MIDP 1.0 is needed. +The client will also need bluetooth and must be able to initialize the connection. +For compilation of the Java Application see Clients/J2ME Client/README but precompiled versions +exists in our forum. + +The Client is mappable in the same manner as PS3 in keymap.xml but with the name J2ME (<joystick name="J2ME">). +The KeyID's generated in terminal when running j2me_remote.py. diff --git a/tools/EventClients/examples/c#/XBMCDemoClient1.cs b/tools/EventClients/examples/c#/XBMCDemoClient1.cs new file mode 100644 index 0000000000..849cc36c07 --- /dev/null +++ b/tools/EventClients/examples/c#/XBMCDemoClient1.cs @@ -0,0 +1,35 @@ +using System;
+using System.Collections.Generic;
+using System.Windows.Forms; +using XBMC;
+
+namespace XBMCEventClientDemo
+{
+ static class Program
+ {
+ /// <summary>
+ /// The main entry point for the application.
+ /// </summary>
+ [STAThread]
+ static void Main()
+ { + EventClient eventClient = new EventClient(); + eventClient.Connect("127.0.0.1", 9777);
+ eventClient.SendHelo("XBMC Client Demo", IconType.ICON_PNG, "icon.png");
+ System.Threading.Thread.Sleep(1000);
+ eventClient.SendNotification("XBMC Client Demo", "Notification Message", IconType.ICON_PNG, "/usr/share/xbmc/media/icon.png");
+ System.Threading.Thread.Sleep(1000);
+ eventClient.SendButton("dpadup", "XG", ButtonFlagsType.BTN_DOWN | ButtonFlagsType.BTN_NO_REPEAT, 0);
+ System.Threading.Thread.Sleep(1000);
+ eventClient.SendPing();
+ System.Threading.Thread.Sleep(1000);
+ eventClient.SendMouse(32768, 32768);
+ System.Threading.Thread.Sleep(1000);
+ eventClient.SendLog(LogTypeEnum.LOGERROR, "Example error log message from XBMC Client Demo");
+ System.Threading.Thread.Sleep(1000);
+ eventClient.SendAction("Mute");
+ System.Threading.Thread.Sleep(1000);
+ eventClient.SendBye();
+ }
+ }
+} diff --git a/tools/EventClients/examples/c++/example_button1.cpp b/tools/EventClients/examples/c++/example_button1.cpp new file mode 100644 index 0000000000..a980e3da24 --- /dev/null +++ b/tools/EventClients/examples/c++/example_button1.cpp @@ -0,0 +1,38 @@ +#include "../../lib/c++/xbmcclient.h" +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> + +int main(int argc, char **argv) +{ + /* connect to localhost, port 9777 using a UDP socket + this only needs to be done once. + by default this is where XBMC will be listening for incoming + connections. */ + CAddress my_addr; // Address => localhost on 9777 + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) + { + printf("Error creating socket\n"); + return -1; + } + + my_addr.Bind(sockfd); + + CPacketHELO HeloPackage("Example Remote", ICON_PNG, "../../icons/bluetooth.png"); + HeloPackage.Send(sockfd, my_addr); + + sleep(5); + // press 'S' + CPacketBUTTON btn1('S', true); + btn1.Send(sockfd, my_addr); + + sleep(2); + // press the enter key (13 = enter) + CPacketBUTTON btn2(13, true); + btn2.Send(sockfd, my_addr); + + // BYE is not required since XBMC would have shut down + CPacketBYE bye; // CPacketPing if you want to ping + bye.Send(sockfd, my_addr); +} diff --git a/tools/EventClients/examples/c++/example_button2.cpp b/tools/EventClients/examples/c++/example_button2.cpp new file mode 100644 index 0000000000..852e49f9a2 --- /dev/null +++ b/tools/EventClients/examples/c++/example_button2.cpp @@ -0,0 +1,48 @@ +#include "../../lib/c++/xbmcclient.h" +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> + +int main(int argc, char **argv) +{ + /* connect to localhost, port 9777 using a UDP socket + this only needs to be done once. + by default this is where XBMC will be listening for incoming + connections. */ + CAddress my_addr; // Address => localhost on 9777 + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) + { + printf("Error creating socket\n"); + return -1; + } + + my_addr.Bind(sockfd); + + CPacketHELO HeloPackage("Example Remote", ICON_PNG, "../../icons/bluetooth.png"); + HeloPackage.Send(sockfd, my_addr); + + sleep(5); + // Note that we have foo(BUTTON, DEVICEMAP); + CPacketBUTTON btn1("dpadup", "XG"); + btn1.Send(sockfd, my_addr); + + sleep(5); + + CPacketBUTTON btn2(0x28); + btn2.Send(sockfd, my_addr); + + sleep(5); + + CPacketBUTTON btn3("right", "KB"); + btn3.Send(sockfd, my_addr); + + sleep(5); + // Release button + CPacketBUTTON btn4; + btn4.Send(sockfd, my_addr); + + // BYE is not required since XBMC would have shut down + CPacketBYE bye; // CPacketPing if you want to ping + bye.Send(sockfd, my_addr); +} diff --git a/tools/EventClients/examples/c++/example_log.cpp b/tools/EventClients/examples/c++/example_log.cpp new file mode 100644 index 0000000000..6194ed9c51 --- /dev/null +++ b/tools/EventClients/examples/c++/example_log.cpp @@ -0,0 +1,34 @@ +#include "../../lib/c++/xbmcclient.h" +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> + +int main(int argc, char **argv) +{ + /* connect to localhost, port 9777 using a UDP socket + this only needs to be done once. + by default this is where XBMC will be listening for incoming + connections. */ + + CAddress my_addr; // Address => localhost on 9777 + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) + { + printf("Error creating socket\n"); + return -1; + } + + my_addr.Bind(sockfd); + //Normally this is already done by the client + CPacketHELO HeloPackage("LOG Test", ICON_NONE); + HeloPackage.Send(sockfd, my_addr); + + sleep(5); + //This works as XBMC internal CLog::LOG(LOGTYPE, STRING); + CPacketLOG packet(LOGERROR, "The Log Message"); + packet.Send(sockfd, my_addr); + + // BYE is not required since XBMC would have shut down + CPacketBYE bye; // CPacketPing if you want to ping + bye.Send(sockfd, my_addr); +} diff --git a/tools/EventClients/examples/c++/example_mouse.cpp b/tools/EventClients/examples/c++/example_mouse.cpp new file mode 100644 index 0000000000..23f40d3de8 --- /dev/null +++ b/tools/EventClients/examples/c++/example_mouse.cpp @@ -0,0 +1,36 @@ +#include "../../lib/c++/xbmcclient.h" +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> + +int main(int argc, char **argv) +{ + /* connect to localhost, port 9777 using a UDP socket + this only needs to be done once. + by default this is where XBMC will be listening for incoming + connections. */ + CAddress my_addr; // Address => localhost on 9777 + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) + { + printf("Error creating socket\n"); + return -1; + } + + my_addr.Bind(sockfd); + + CPacketHELO HeloPackage("Example Mouse", ICON_PNG, "../../icons/mouse.png"); + HeloPackage.Send(sockfd, my_addr); + + sleep(5); + + for(int i = 0; i < 65536; i++) + { + CPacketMOUSE mouse(i,i); + mouse.Send(sockfd, my_addr); + } + + // BYE is not required since XBMC would have shut down + CPacketBYE bye; // CPacketPing if you want to ping + bye.Send(sockfd, my_addr); +} diff --git a/tools/EventClients/examples/c++/example_notification.cpp b/tools/EventClients/examples/c++/example_notification.cpp new file mode 100644 index 0000000000..dc51075b85 --- /dev/null +++ b/tools/EventClients/examples/c++/example_notification.cpp @@ -0,0 +1,37 @@ +#include "../../lib/c++/xbmcclient.h" +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> + +int main(int argc, char **argv) +{ + /* connect to localhost, port 9777 using a UDP socket + this only needs to be done once. + by default this is where XBMC will be listening for incoming + connections. */ + + CAddress my_addr; // Address => localhost on 9777 + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) + { + printf("Error creating socket\n"); + return -1; + } + + my_addr.Bind(sockfd); + + CPacketHELO HeloPackage("Email Notifier", ICON_NONE); + HeloPackage.Send(sockfd, my_addr); + + sleep(5); + + CPacketNOTIFICATION packet("New Mail!", // caption + "RE: Check this out", // message + ICON_PNG, // optional icon type + "../../icons/mail.png"); // icon file (local) + packet.Send(sockfd, my_addr); + + // BYE is not required since XBMC would have shut down + CPacketBYE bye; // CPacketPing if you want to ping + bye.Send(sockfd, my_addr); +} diff --git a/tools/EventClients/examples/java/XBMCDemoClient1.java b/tools/EventClients/examples/java/XBMCDemoClient1.java new file mode 100644 index 0000000000..0be3865cbd --- /dev/null +++ b/tools/EventClients/examples/java/XBMCDemoClient1.java @@ -0,0 +1,52 @@ +package org.xbmc.eventclient.demo; + +import java.io.IOException; +import java.net.Inet4Address; +import java.net.InetAddress; + +import org.xbmc.eventclient.XBMCClient; + +/** + * Simple Demo EventClient + * @author Stefan Agner + * + */ +public class XBMCDemoClient1 { + + /** + * Simple Demo EventClient + * @param args + */ + public static void main(String[] args) throws IOException, InterruptedException { + InetAddress host = Inet4Address.getByAddress(new byte[] { (byte)192, (byte)168, 0, 20 } ); + + Thread.sleep(20000); + XBMCClient oXBMCClient = new XBMCClient(host, 9777, "My Client", "/usr/share/xbmc/media/icon.png"); + + Thread.sleep(7000); + + oXBMCClient.sendNotification("My Title", "My Message"); + + + Thread.sleep(7000); + + oXBMCClient.sendButton("KB", "escape", false, true, false, (short)0 , (byte)0); + + + Thread.sleep(7000); + oXBMCClient.sendButton("KB", "escape", true, true, false, (short)0 , (byte)0); + oXBMCClient.sendNotification("My Title", "Escape sent"); + + Thread.sleep(1000); + + oXBMCClient.sendButton("KB", "escape", true, false, false, (short)0 , (byte)0); + oXBMCClient.sendNotification("My Title", "Escape released"); + + Thread.sleep(7000); + oXBMCClient.sendLog((byte)0, "My Client disconnects...."); + oXBMCClient.sendNotification("My Title", "Client will disconnect"); + oXBMCClient.stopClient(); + + } + +} diff --git a/tools/EventClients/examples/python/example_action.py b/tools/EventClients/examples/python/example_action.py new file mode 100755 index 0000000000..d5ab9cf61c --- /dev/null +++ b/tools/EventClients/examples/python/example_action.py @@ -0,0 +1,40 @@ +#!/usr/bin/python + +# This is a simple example showing how you can send a key press event +# to XBMC using the XBMCClient class + +import sys +sys.path.append("../../lib/python") + +import time +from xbmcclient import XBMCClient,ACTION_EXECBUILTIN,ACTION_BUTTON + +def main(): + + host = "localhost" + port = 9777 + + # Create an XBMCClient object and connect + xbmc = XBMCClient("Example Remote", "../../icons/bluetooth.png") + xbmc.connect() + + # send a up key press using the xbox gamepad map "XG" and button + # name "dpadup" ( see PacketBUTTON doc for more details) + try: + xbmc.send_action(sys.argv[2], ACTION_BUTTON) + except: + try: + xbmc.send_action(sys.argv[1], ACTION_EXECBUILTIN) + except Exception, e: + print str(e) + xbmc.send_action("XBMC.ActivateWindow(ShutdownMenu)") + + + # ok we're done, close the connection + # Note that closing the connection clears any repeat key that is + # active. So in this example, the actual release button event above + # need not have been sent. + xbmc.close() + +if __name__=="__main__": + main() diff --git a/tools/EventClients/examples/python/example_button1.py b/tools/EventClients/examples/python/example_button1.py new file mode 100755 index 0000000000..7b208252cb --- /dev/null +++ b/tools/EventClients/examples/python/example_button1.py @@ -0,0 +1,79 @@ +#!/usr/bin/python + +# This is a simple example showing how you can send 2 button events +# to XBMC in a queued fashion to shut it down. + +# Queued button events are not repeatable. + +# The basic idea is to create single packets and shoot them to XBMC +# The provided library implements some of the support commands and +# takes care of creating the actual packet. Using it is as simple +# as creating an object with the required constructor arguments and +# sending it through a socket. + +# Currently, only keyboard keys are supported so the key codes used +# below are the same key codes used in guilib/common/SDLKeyboard.cpp + +# In effect, anything that can be done with the keyboard can be done +# using the event client. + +# import the XBMC client library +# NOTE: The library is not complete yet but is usable at this stage. + +import sys +sys.path.append("../../lib/python") + +from xbmcclient import * +from socket import * + +def main(): + import time + import sys + + # connect to localhost, port 9777 using a UDP socket + # this only needs to be done once. + # by default this is where XBMC will be listening for incoming + # connections. + host = "localhost" + port = 9777 + addr = (host, port) + sock = socket(AF_INET,SOCK_DGRAM) + + # First packet must be HELO (no it's not a typo) and can contain an icon + # 'icon_type' can be one of ICON_NONE, ICON_PNG, ICON_JPG or ICON_GIF + packet = PacketHELO(devicename="Example Remote", + icon_type=ICON_PNG, + icon_file="../../icons/bluetooth.png") + packet.send(sock, addr) + + # IMPORTANT: After a HELO packet is sent, the client needs to "ping" XBMC + # at least once every 60 seconds or else the client will time out. + # Every valid packet sent to XBMC acts as a ping, however if no valid + # packets NEED to be sent (eg. the user hasn't pressed a key in 50 seconds) + # then you can use the PacketPING class to send a ping packet (which is + # basically just an emppty packet). See below. + + # Once a client times out, it will need to reissue the HELO packet. + # Currently, since this is a unidirectional protocol, there is no way + # for the client to know if it has timed out. + + # wait for notification window to close (in XBMC) + time.sleep(5) + + # press 'S' + packet = PacketBUTTON(code='S', queue=1) + packet.send(sock, addr) + + # wait for a few seconds + time.sleep(2) + + # press the enter key (13 = enter) + packet = PacketBUTTON(code=13, queue=1) + packet.send(sock, addr) + + # BYE is not required since XBMC would have shut down + packet = PacketBYE() # PacketPING if you want to ping + packet.send(sock, addr) + +if __name__=="__main__": + main() diff --git a/tools/EventClients/examples/python/example_button2.py b/tools/EventClients/examples/python/example_button2.py new file mode 100755 index 0000000000..b420bd84b8 --- /dev/null +++ b/tools/EventClients/examples/python/example_button2.py @@ -0,0 +1,73 @@ +#!/usr/bin/python + +# This is a simple example showing how you can send a key press event +# to XBMC in a non-queued fashion to achieve a button pressed down +# event i.e. a key press that repeats. + +# The repeat interval is currently hard coded in XBMC but that might +# change in the future. + +# NOTE: Read the comments in 'example_button1.py' for a more detailed +# explanation. + +import sys +sys.path.append("../../lib/python") + +from xbmcclient import * +from socket import * + +def main(): + import time + import sys + + host = "localhost" + port = 9777 + addr = (host, port) + + sock = socket(AF_INET,SOCK_DGRAM) + + # First packet must be HELO and can contain an icon + packet = PacketHELO("Example Remote", ICON_PNG, + "../../icons/bluetooth.png") + packet.send(sock, addr) + + # wait for notification window to close (in XBMC) + time.sleep(5) + + # send a up key press using the xbox gamepad map "XG" and button + # name "dpadup" ( see PacketBUTTON doc for more details) + packet = PacketBUTTON(map_name="XG", button_name="dpadup") + packet.send(sock, addr) + + # wait for a few seconds to see its effect + time.sleep(5) + + # send a down key press using the raw keyboard code + packet = PacketBUTTON(code=0x28) + packet.send(sock, addr) + + # wait for a few seconds to see its effect + time.sleep(5) + + # send a right key press using the keyboard map "KB" and button + # name "right" + packet = PacketBUTTON(map_name="KB", button_name="right") + packet.send(sock, addr) + + # wait for a few seconds to see its effect + time.sleep(5) + + # that's enough, release the button. During release, button code + # doesn't matter. + packet = PacketBUTTON(code=0x28, down=0) + packet.send(sock, addr) + + # ok we're done, close the connection + # Note that closing the connection clears any repeat key that is + # active. So in this example, the actual release button event above + # need not have been sent. + packet = PacketBYE() + packet.send(sock, addr) + +if __name__=="__main__": + main() diff --git a/tools/EventClients/examples/python/example_mouse.py b/tools/EventClients/examples/python/example_mouse.py new file mode 100755 index 0000000000..10d7f61ba7 --- /dev/null +++ b/tools/EventClients/examples/python/example_mouse.py @@ -0,0 +1,45 @@ +#!/usr/bin/python + +# This is a simple example showing how you can send mouse movement +# events to XBMC. + +# NOTE: Read the comments in 'example_button1.py' for a more detailed +# explanation. + +import sys +sys.path.append("../../lib/python") + +from xbmcclient import * +from socket import * + +def main(): + import time + import sys + + host = "localhost" + port = 9777 + addr = (host, port) + + sock = socket(AF_INET,SOCK_DGRAM) + + # First packet must be HELO and can contain an icon + packet = PacketHELO("Example Mouse", ICON_PNG, + "../../icons/mouse.png") + packet.send(sock, addr) + + # wait for notification window to close (in XBMC) + time.sleep(2) + + # send mouse events to take cursor from top left to bottom right of the screen + # here 0 to 65535 will map to XBMC's screen width and height. + # Specifying absolute mouse coordinates is unsupported currently. + for i in range(0, 65535, 2): + packet = PacketMOUSE(i,i) + packet.send(sock, addr) + + # ok we're done, close the connection + packet = PacketBYE() + packet.send(sock, addr) + +if __name__=="__main__": + main() diff --git a/tools/EventClients/examples/python/example_notification.py b/tools/EventClients/examples/python/example_notification.py new file mode 100755 index 0000000000..81bbadccd2 --- /dev/null +++ b/tools/EventClients/examples/python/example_notification.py @@ -0,0 +1,38 @@ +#!/usr/bin/python + +# This is a simple example showing how you can show a notification +# window with a custom icon inside XBMC. It could be used by mail +# monitoring apps, calendar apps, etc. + +import sys +sys.path.append("../../lib/python") + +from xbmcclient import * +from socket import * + +def main(): + import time + import sys + + host = "localhost" + port = 9777 + addr = (host, port) + sock = socket(AF_INET,SOCK_DGRAM) + + packet = PacketHELO("Email Notifier", ICON_NONE) + packet.send(sock, addr) + + # wait for 5 seconds + time.sleep (5) + + packet = PacketNOTIFICATION("New Mail!", # caption + "RE: Check this out", # message + ICON_PNG, # optional icon type + "../../icons/mail.png") # icon file (local) + packet.send(sock, addr) + + packet = PacketBYE() + packet.send(sock, addr) + +if __name__=="__main__": + main() diff --git a/tools/EventClients/examples/python/example_simple.py b/tools/EventClients/examples/python/example_simple.py new file mode 100755 index 0000000000..6218131e13 --- /dev/null +++ b/tools/EventClients/examples/python/example_simple.py @@ -0,0 +1,48 @@ +#!/usr/bin/python + +# This is a simple example showing how you can send a key press event +# to XBMC using the XBMCClient class + +import sys +sys.path.append("../../lib/python") + +import time +from xbmcclient import XBMCClient + +def main(): + + host = "localhost" + port = 9777 + + # Create an XBMCClient object and connect + xbmc = XBMCClient("Example Remote", "../../icons/bluetooth.png") + xbmc.connect() + + # wait for notification window to close (in XBMC) (optional) + time.sleep(5) + + # send a up key press using the xbox gamepad map "XG" and button + # name "dpadup" ( see PacketBUTTON doc for more details) + xbmc.send_button(map="XG", button="dpadup") + + # wait for a few seconds to see its effect + time.sleep(5) + + # send a right key press using the keyboard map "KB" and button + # name "right" + xbmc.send_keyboard_button("right") + + # wait for a few seconds to see its effect + time.sleep(5) + + # that's enough, release the button. + xbmc.release_button() + + # ok we're done, close the connection + # Note that closing the connection clears any repeat key that is + # active. So in this example, the actual release button event above + # need not have been sent. + xbmc.close() + +if __name__=="__main__": + main() diff --git a/tools/EventClients/icons/bluetooth.png b/tools/EventClients/icons/bluetooth.png Binary files differnew file mode 100644 index 0000000000..5ede31fd45 --- /dev/null +++ b/tools/EventClients/icons/bluetooth.png diff --git a/tools/EventClients/icons/mail.png b/tools/EventClients/icons/mail.png Binary files differnew file mode 100644 index 0000000000..7c68b2e854 --- /dev/null +++ b/tools/EventClients/icons/mail.png diff --git a/tools/EventClients/icons/mouse.png b/tools/EventClients/icons/mouse.png Binary files differnew file mode 100644 index 0000000000..c0e165760c --- /dev/null +++ b/tools/EventClients/icons/mouse.png diff --git a/tools/EventClients/icons/phone.png b/tools/EventClients/icons/phone.png Binary files differnew file mode 100644 index 0000000000..3db1682eeb --- /dev/null +++ b/tools/EventClients/icons/phone.png diff --git a/tools/EventClients/lib/c#/EventClient.cs b/tools/EventClients/lib/c#/EventClient.cs new file mode 100644 index 0000000000..f904dcaa93 --- /dev/null +++ b/tools/EventClients/lib/c#/EventClient.cs @@ -0,0 +1,529 @@ +using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+
+namespace XBMC
+{
+
+ public enum IconType
+ {
+ ICON_NONE = 0x00,
+ ICON_JPEG = 0x01,
+ ICON_PNG = 0x02,
+ ICON_GIF = 0x03
+ }
+
+ public enum ButtonFlagsType
+ {
+ BTN_USE_NAME = 0x01,
+ BTN_DOWN = 0x02,
+ BTN_UP = 0x04,
+ BTN_USE_AMOUNT = 0x08,
+ BTN_QUEUE = 0x10,
+ BTN_NO_REPEAT = 0x20,
+ BTN_VKEY = 0x40,
+ BTN_AXIS = 0x80
+ }
+
+ public enum MouseFlagsType
+ {
+ MS_ABSOLUTE = 0x01
+ }
+
+ public enum LogTypeEnum
+ {
+ LOGDEBUG = 0,
+ LOGINFO = 1,
+ LOGNOTICE = 2,
+ LOGWARNING = 3,
+ LOGERROR = 4,
+ LOGSEVERE = 5,
+ LOGFATAL = 6,
+ LOGNONE = 7
+ }
+
+ public enum ActionType
+ {
+ ACTION_EXECBUILTIN = 0x01,
+ ACTION_BUTTON = 0x02
+ }
+
+ public class EventClient
+ {
+
+ /************************************************************************/
+ /* Written by Peter Tribe aka EqUiNox (TeamBlackbolt) */
+ /* Based upon XBMC's xbmcclient.cpp class */
+ /************************************************************************/
+
+ private enum PacketType
+ {
+ PT_HELO = 0x01,
+ PT_BYE = 0x02,
+ PT_BUTTON = 0x03,
+ PT_MOUSE = 0x04,
+ PT_PING = 0x05,
+ PT_BROADCAST = 0x06, //Currently not implemented
+ PT_NOTIFICATION = 0x07,
+ PT_BLOB = 0x08,
+ PT_LOG = 0x09,
+ PT_ACTION = 0x0A,
+ PT_DEBUG = 0xFF //Currently not implemented
+ }
+
+ private const int STD_PORT = 9777;
+ private const int MAX_PACKET_SIZE = 1024;
+ private const int HEADER_SIZE = 32;
+ private const int MAX_PAYLOAD_SIZE = MAX_PACKET_SIZE - HEADER_SIZE;
+ private const byte MAJOR_VERSION = 2;
+ private const byte MINOR_VERSION = 0;
+
+ private uint uniqueToken;
+ private Socket socket;
+
+ public bool Connect(string Address)
+ {
+ return Connect(Address, STD_PORT, (uint)System.DateTime.Now.TimeOfDay.Milliseconds);
+ }
+
+ public bool Connect(string Address, int Port)
+ {
+ return Connect(Address, Port, (uint)System.DateTime.Now.TimeOfDay.Milliseconds);
+ }
+
+
+ public bool Connect(string Address, int Port, uint UID)
+ {
+ try
+ {
+ if (socket != null) Disconnect();
+ uniqueToken = UID;
+ socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ socket.Connect(Dns.GetHostByName(Address).AddressList[0].ToString(), Port);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ public bool Connected
+ {
+ get
+ {
+ if (socket == null) return false;
+ return socket.Connected;
+ }
+ }
+
+ public void Disconnect()
+ {
+ try
+ {
+ if (socket != null)
+ {
+ socket.Shutdown(SocketShutdown.Both);
+ socket.Close();
+ socket = null;
+ }
+ }
+ catch
+ {
+ }
+ }
+
+ private byte[] Header(PacketType PacketType, int NumberOfPackets, int CurrentPacket, int PayloadSize)
+ {
+
+ byte[] header = new byte[HEADER_SIZE];
+
+ header[0] = (byte)'X';
+ header[1] = (byte)'B';
+ header[2] = (byte)'M';
+ header[3] = (byte)'C';
+
+ header[4] = MAJOR_VERSION;
+ header[5] = MINOR_VERSION;
+
+ if (CurrentPacket == 1)
+ {
+ header[6] = (byte)(((ushort)PacketType & 0xff00) >> 8);
+ header[7] = (byte)((ushort)PacketType & 0x00ff);
+ }
+ else
+ {
+ header[6] = (byte)(((ushort)PacketType.PT_BLOB & 0xff00) >> 8);
+ header[7] = (byte)((ushort)PacketType.PT_BLOB & 0x00ff);
+ }
+
+ header[8] = (byte)((CurrentPacket & 0xff000000) >> 24);
+ header[9] = (byte)((CurrentPacket & 0x00ff0000) >> 16);
+ header[10] = (byte)((CurrentPacket & 0x0000ff00) >> 8);
+ header[11] = (byte)(CurrentPacket & 0x000000ff);
+
+ header[12] = (byte)((NumberOfPackets & 0xff000000) >> 24);
+ header[13] = (byte)((NumberOfPackets & 0x00ff0000) >> 16);
+ header[14] = (byte)((NumberOfPackets & 0x0000ff00) >> 8);
+ header[15] = (byte)(NumberOfPackets & 0x000000ff);
+
+ header[16] = (byte)((PayloadSize & 0xff00) >> 8);
+ header[17] = (byte)(PayloadSize & 0x00ff);
+
+ header[18] = (byte)((uniqueToken & 0xff000000) >> 24);
+ header[19] = (byte)((uniqueToken & 0x00ff0000) >> 16);
+ header[20] = (byte)((uniqueToken & 0x0000ff00) >> 8);
+ header[21] = (byte)(uniqueToken & 0x000000ff);
+
+ return header;
+
+ }
+
+ private bool Send(PacketType PacketType, byte[] Payload)
+ {
+ try
+ {
+
+ bool successfull = true;
+ int packetCount = (Payload.Length / MAX_PAYLOAD_SIZE) + 1;
+ int bytesToSend = 0;
+ int bytesSent = 0;
+ int bytesLeft = Payload.Length;
+
+ for (int Package = 1; Package <= packetCount; Package++)
+ {
+
+ if (bytesLeft > MAX_PAYLOAD_SIZE)
+ {
+ bytesToSend = MAX_PAYLOAD_SIZE;
+ bytesLeft -= bytesToSend;
+ }
+ else
+ {
+ bytesToSend = bytesLeft;
+ bytesLeft = 0;
+ }
+
+ byte[] header = Header(PacketType, packetCount, Package, bytesToSend);
+ byte[] packet = new byte[MAX_PACKET_SIZE];
+
+ Array.Copy(header, 0, packet, 0, header.Length);
+ Array.Copy(Payload, bytesSent, packet, header.Length, bytesToSend);
+
+ int sendSize = socket.Send(packet, header.Length + bytesToSend, SocketFlags.None);
+
+ if (sendSize != (header.Length + bytesToSend))
+ {
+ successfull = false;
+ break;
+ }
+
+ bytesSent += bytesToSend;
+
+ }
+
+ return successfull;
+
+ }
+ catch
+ {
+
+ return false;
+
+ }
+
+ }
+
+ /************************************************************************/
+ /* SendHelo - Payload format */
+ /* %s - device name (max 128 chars) */
+ /* %c - icontype ( 0=>NOICON, 1=>JPEG , 2=>PNG , 3=>GIF ) */
+ /* %s - my port ( 0=>not listening ) */
+ /* %d - reserved1 ( 0 ) */
+ /* %d - reserved2 ( 0 ) */
+ /* XX - imagedata ( can span multiple packets ) */
+ /************************************************************************/
+ public bool SendHelo(string DevName, IconType IconType, string IconFile)
+ {
+
+ byte[] icon = new byte[0];
+ if (IconType != IconType.ICON_NONE)
+ icon = File.ReadAllBytes(IconFile);
+
+ byte[] payload = new byte[DevName.Length + 12 + icon.Length];
+
+ int offset = 0;
+
+ for (int i = 0; i < DevName.Length; i++)
+ payload[offset++] = (byte)DevName[i];
+ payload[offset++] = (byte)'\0';
+
+ payload[offset++] = (byte)IconType;
+
+ payload[offset++] = (byte)0;
+ payload[offset++] = (byte)'\0';
+
+ for (int i = 0; i < 8; i++)
+ payload[offset++] = (byte)0;
+
+ Array.Copy(icon, 0, payload, DevName.Length + 12, icon.Length);
+
+ return Send(PacketType.PT_HELO, payload);
+
+ }
+
+ public bool SendHelo(string DevName)
+ {
+ return SendHelo(DevName, IconType.ICON_NONE, "");
+ }
+
+ /************************************************************************/
+ /* SendNotification - Payload format */
+ /* %s - caption */
+ /* %s - message */
+ /* %c - icontype ( 0=>NOICON, 1=>JPEG , 2=>PNG , 3=>GIF ) */
+ /* %d - reserved ( 0 ) */
+ /* XX - imagedata ( can span multiple packets ) */
+ /************************************************************************/
+ public bool SendNotification(string Caption, string Message, IconType IconType, string IconFile)
+ {
+
+ byte[] icon = new byte[0];
+ if (IconType != IconType.ICON_NONE)
+ icon = File.ReadAllBytes(IconFile);
+
+ byte[] payload = new byte[Caption.Length + Message.Length + 7 + icon.Length];
+
+ int offset = 0;
+
+ for (int i = 0; i < Caption.Length; i++)
+ payload[offset++] = (byte)Caption[i];
+ payload[offset++] = (byte)'\0';
+
+ for (int i = 0; i < Message.Length; i++)
+ payload[offset++] = (byte)Message[i];
+ payload[offset++] = (byte)'\0';
+
+ payload[offset++] = (byte)IconType;
+
+ for (int i = 0; i < 4; i++)
+ payload[offset++] = (byte)0;
+
+ Array.Copy(icon, 0, payload, Caption.Length + Message.Length + 7, icon.Length);
+
+ return Send(PacketType.PT_NOTIFICATION, payload);
+
+ }
+
+ public bool SendNotification(string Caption, string Message)
+ {
+ return SendNotification(Caption, Message, IconType.ICON_NONE, "");
+ }
+
+ /************************************************************************/
+ /* SendButton - Payload format */
+ /* %i - button code */
+ /* %i - flags 0x01 => use button map/name instead of code */
+ /* 0x02 => btn down */
+ /* 0x04 => btn up */
+ /* 0x08 => use amount */
+ /* 0x10 => queue event */
+ /* 0x20 => do not repeat */
+ /* 0x40 => virtual key */
+ /* 0x80 => axis key */
+ /* %i - amount ( 0 => 65k maps to -1 => 1 ) */
+ /* %s - device map (case sensitive and required if flags & 0x01) */
+ /* "KB" - Standard keyboard map */
+ /* "XG" - Xbox Gamepad */
+ /* "R1" - Xbox Remote */
+ /* "R2" - Xbox Universal Remote */
+ /* "LI:devicename" - valid LIRC device map where 'devicename' */
+ /* is the actual name of the LIRC device */
+ /* "JS<num>:joyname" - valid Joystick device map where */
+ /* 'joyname' is the name specified in */
+ /* the keymap. JS only supports button code */
+ /* and not button name currently (!0x01). */
+ /* %s - button name (required if flags & 0x01) */
+ /************************************************************************/
+ private bool SendButton(string Button, ushort ButtonCode, string DeviceMap, ButtonFlagsType Flags, short Amount)
+ {
+
+ if (Button.Length != 0)
+ {
+ if ((Flags & ButtonFlagsType.BTN_USE_NAME) == 0)
+ Flags |= ButtonFlagsType.BTN_USE_NAME;
+ ButtonCode = 0;
+ }
+ else
+ Button = "";
+
+ if (Amount > 0)
+ {
+ if ((Flags & ButtonFlagsType.BTN_USE_AMOUNT) == 0)
+ Flags |= ButtonFlagsType.BTN_USE_AMOUNT;
+ }
+
+ if ((Flags & ButtonFlagsType.BTN_DOWN) == 0 || (Flags & ButtonFlagsType.BTN_UP) == 0)
+ Flags |= ButtonFlagsType.BTN_DOWN;
+
+ byte[] payload = new byte[Button.Length + DeviceMap.Length + 8];
+
+ int offset = 0;
+
+ payload[offset++] = (byte)((ButtonCode & 0xff00) >> 8);
+ payload[offset++] = (byte)(ButtonCode & 0x00ff);
+
+ payload[offset++] = (byte)(((ushort)Flags & 0xff00) >> 8);
+ payload[offset++] = (byte)((ushort)Flags & 0x00ff);
+
+ payload[offset++] = (byte)((Amount & 0xff00) >> 8);
+ payload[offset++] = (byte)(Amount & 0x00ff);
+
+ for (int i = 0; i < DeviceMap.Length; i++)
+ payload[offset++] = (byte)DeviceMap[i];
+ payload[offset++] = (byte)'\0';
+
+ for (int i = 0; i < Button.Length; i++)
+ payload[offset++] = (byte)Button[i];
+ payload[offset++] = (byte)'\0';
+
+ return Send(PacketType.PT_BUTTON, payload);
+
+ }
+
+ public bool SendButton(string Button, string DeviceMap, ButtonFlagsType Flags, short Amount)
+ {
+ return SendButton(Button, 0, DeviceMap, Flags, Amount);
+ }
+
+ public bool SendButton(string Button, string DeviceMap, ButtonFlagsType Flags)
+ {
+ return SendButton(Button, 0, DeviceMap, Flags, 0);
+ }
+
+ public bool SendButton(ushort ButtonCode, string DeviceMap, ButtonFlagsType Flags, short Amount)
+ {
+ return SendButton("", ButtonCode, DeviceMap, Flags, Amount);
+ }
+
+ public bool SendButton(ushort ButtonCode, string DeviceMap, ButtonFlagsType Flags)
+ {
+ return SendButton("", ButtonCode, DeviceMap, Flags, 0);
+ }
+
+ public bool SendButton(ushort ButtonCode, ButtonFlagsType Flags, short Amount)
+ {
+ return SendButton("", ButtonCode, "", Flags, Amount);
+ }
+
+ public bool SendButton(ushort ButtonCode, ButtonFlagsType Flags)
+ {
+ return SendButton("", ButtonCode, "", Flags, 0);
+ }
+
+ public bool SendButton()
+ {
+ return SendButton("", 0, "", ButtonFlagsType.BTN_UP, 0);
+ }
+
+ /************************************************************************/
+ /* SendPing - No payload */
+ /************************************************************************/
+ public bool SendPing()
+ {
+ byte[] payload = new byte[0];
+ return Send(PacketType.PT_PING, payload);
+ }
+
+ /************************************************************************/
+ /* SendBye - No payload */
+ /************************************************************************/
+ public bool SendBye()
+ {
+ byte[] payload = new byte[0];
+ return Send(PacketType.PT_BYE, payload);
+ }
+
+ /************************************************************************/
+ /* SendMouse - Payload format */
+ /* %c - flags */
+ /* - 0x01 absolute position */
+ /* %i - mousex (0-65535 => maps to screen width) */
+ /* %i - mousey (0-65535 => maps to screen height) */
+ /************************************************************************/
+ public bool SendMouse(ushort X, ushort Y, MouseFlagsType Flags)
+ {
+
+ byte[] payload = new byte[9];
+
+ int offset = 0;
+
+ payload[offset++] = (byte)Flags;
+
+ payload[offset++] = (byte)((X & 0xff00) >> 8);
+ payload[offset++] = (byte)(X & 0x00ff);
+
+ payload[offset++] = (byte)((Y & 0xff00) >> 8);
+ payload[offset++] = (byte)(Y & 0x00ff);
+
+ return Send(PacketType.PT_MOUSE, payload);
+
+ }
+
+ public bool SendMouse(ushort X, ushort Y)
+ {
+ return SendMouse(X, Y, MouseFlagsType.MS_ABSOLUTE);
+ }
+
+ /************************************************************************/
+ /* SendLog - Payload format */
+ /* %c - log type */
+ /* %s - message */
+ /************************************************************************/
+ public bool SendLog(LogTypeEnum LogLevel, string Message)
+ {
+
+ byte[] payload = new byte[Message.Length + 2];
+
+ int offset = 0;
+
+ payload[offset++] = (byte)LogLevel;
+
+ for (int i = 0; i < Message.Length; i++)
+ payload[offset++] = (byte)Message[i];
+ payload[offset++] = (byte)'\0';
+
+ return Send(PacketType.PT_LOG, payload);
+
+ }
+
+ /************************************************************************/
+ /* SendAction - Payload format */
+ /* %c - action type */
+ /* %s - action message */
+ /************************************************************************/
+ public bool SendAction(ActionType Action, string Message)
+ {
+
+ byte[] payload = new byte[Message.Length + 2];
+
+ int offset = 0;
+
+ payload[offset++] = (byte)Action;
+
+ for (int i = 0; i < Message.Length; i++)
+ payload[offset++] = (byte)Message[i];
+ payload[offset++] = (byte)'\0';
+
+ return Send(PacketType.PT_ACTION, payload);
+
+ }
+
+ public bool SendAction(string Message)
+ {
+ return SendAction(ActionType.ACTION_EXECBUILTIN, Message);
+ }
+
+ }
+}
diff --git a/tools/EventClients/lib/c++/xbmcclient.h b/tools/EventClients/lib/c++/xbmcclient.h new file mode 100644 index 0000000000..f7456baef8 --- /dev/null +++ b/tools/EventClients/lib/c++/xbmcclient.h @@ -0,0 +1,816 @@ +#ifndef __XBMC_CLIENT_H__ +#define __XBMC_CLIENT_H__ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#ifdef _WIN32 +#include <winsock.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <arpa/inet.h> +#endif +#include <vector> +#include <iostream> +#include <fstream> +#include <time.h> + +#define STD_PORT 9777 + +#define MS_ABSOLUTE 0x01 +//#define MS_RELATIVE 0x02 + +#define BTN_USE_NAME 0x01 +#define BTN_DOWN 0x02 +#define BTN_UP 0x04 +#define BTN_USE_AMOUNT 0x08 +#define BTN_QUEUE 0x10 +#define BTN_NO_REPEAT 0x20 +#define BTN_VKEY 0x40 +#define BTN_AXIS 0x80 + +#define PT_HELO 0x01 +#define PT_BYE 0x02 +#define PT_BUTTON 0x03 +#define PT_MOUSE 0x04 +#define PT_PING 0x05 +#define PT_BROADCAST 0x06 +#define PT_NOTIFICATION 0x07 +#define PT_BLOB 0x08 +#define PT_LOG 0x09 +#define PT_ACTION 0x0A +#define PT_DEBUG 0xFF + +#define ICON_NONE 0x00 +#define ICON_JPEG 0x01 +#define ICON_PNG 0x02 +#define ICON_GIF 0x03 + +#define MAX_PACKET_SIZE 1024 +#define HEADER_SIZE 32 +#define MAX_PAYLOAD_SIZE (MAX_PACKET_SIZE - HEADER_SIZE) + +#define MAJOR_VERSION 2 +#define MINOR_VERSION 0 + +#define LOGDEBUG 0 +#define LOGINFO 1 +#define LOGNOTICE 2 +#define LOGWARNING 3 +#define LOGERROR 4 +#define LOGSEVERE 5 +#define LOGFATAL 6 +#define LOGNONE 7 + +#define ACTION_EXECBUILTIN 0x01 +#define ACTION_BUTTON 0x02 + +class CAddress +{ +private: + struct sockaddr_in m_Addr; +public: + CAddress(int Port = STD_PORT) + { + m_Addr.sin_family = AF_INET; + m_Addr.sin_port = htons(Port); + m_Addr.sin_addr.s_addr = INADDR_ANY; + memset(m_Addr.sin_zero, '\0', sizeof m_Addr.sin_zero); + } + + CAddress(const char *Address, int Port = STD_PORT) + { + m_Addr.sin_port = htons(Port); + + struct hostent *h; + if (Address == NULL || (h=gethostbyname(Address)) == NULL) + { + if (Address != NULL) + printf("Error: Get host by name\n"); + + m_Addr.sin_addr.s_addr = INADDR_ANY; + m_Addr.sin_family = AF_INET; + } + else + { + m_Addr.sin_family = h->h_addrtype; + m_Addr.sin_addr = *((struct in_addr *)h->h_addr); + } + memset(m_Addr.sin_zero, '\0', sizeof m_Addr.sin_zero); + } + + void SetPort(int port) + { + m_Addr.sin_port = htons(port); + } + + const sockaddr *GetAddress() + { + return ((struct sockaddr *)&m_Addr); + } + + bool Bind(int Sockfd) + { + return (bind(Sockfd, (struct sockaddr *)&m_Addr, sizeof m_Addr) == 0); + } +}; + +class XBMCClientUtils +{ +public: + XBMCClientUtils() {} + ~XBMCClientUtils() {} + static unsigned int GetUniqueIdentifier() + { + static time_t id = time(NULL); + return id; + } + + static void Clean() + { + #ifdef _WIN32 + WSACleanup(); + #endif + } + + static bool Initialize() + { + #ifdef _WIN32 + WSADATA wsaData; + if (WSAStartup(MAKEWORD(1, 1), &wsaData)) + return false; + #endif + return true; + } +}; + +class CPacket +{ +/* Base class that implements a single event packet. + + - Generic packet structure (maximum 1024 bytes per packet) + - Header is 32 bytes long, so 992 bytes available for payload + - large payloads can be split into multiple packets using H4 and H5 + H5 should contain total no. of packets in such a case + - H6 contains length of P1, which is limited to 992 bytes + - if H5 is 0 or 1, then H4 will be ignored (single packet msg) + - H7 must be set to zeros for now + + ----------------------------- + | -H1 Signature ("XBMC") | - 4 x CHAR 4B + | -H2 Version (eg. 2.0) | - 2 x UNSIGNED CHAR 2B + | -H3 PacketType | - 1 x UNSIGNED SHORT 2B + | -H4 Sequence number | - 1 x UNSIGNED LONG 4B + | -H5 No. of packets in msg | - 1 x UNSIGNED LONG 4B + | -H6 Payload size | - 1 x UNSIGNED SHORT 2B + | -H7 Client's unique token | - 1 x UNSIGNED LONG 4B + | -H8 Reserved | - 10 x UNSIGNED CHAR 10B + |---------------------------| + | -P1 payload | - + ----------------------------- +*/ +public: + CPacket() + { + m_PacketType = 0; + } + virtual ~CPacket() + { } + + bool Send(int Socket, CAddress &Addr, unsigned int UID = XBMCClientUtils::GetUniqueIdentifier()) + { + if (m_Payload.size() == 0) + ConstructPayload(); + bool SendSuccessfull = true; + int NbrOfPackages = (m_Payload.size() / MAX_PAYLOAD_SIZE) + 1; + int Send = 0; + int Sent = 0; + int Left = m_Payload.size(); + for (int Package = 1; Package <= NbrOfPackages; Package++) + { + if (Left > MAX_PAYLOAD_SIZE) + { + Send = MAX_PAYLOAD_SIZE; + Left -= Send; + } + else + { + Send = Left; + Left = 0; + } + + ConstructHeader(m_PacketType, NbrOfPackages, Package, Send, UID, m_Header); + char t[MAX_PACKET_SIZE]; + int i, j; + for (i = 0; i < 32; i++) + t[i] = m_Header[i]; + + for (j = 0; j < Send; j++) + t[(32 + j)] = m_Payload[j + Sent]; + + int rtn = sendto(Socket, t, (32 + Send), 0, Addr.GetAddress(), sizeof(struct sockaddr)); + + if (rtn != (32 + Send)) + SendSuccessfull = false; + + Sent += Send; + } + return SendSuccessfull; + } +protected: + char m_Header[HEADER_SIZE]; + unsigned short m_PacketType; + + std::vector<char> m_Payload; + + static void ConstructHeader(int PacketType, int NumberOfPackets, int CurrentPacket, unsigned short PayloadSize, unsigned int UniqueToken, char *Header) + { + sprintf(Header, "XBMC"); + for (int i = 4; i < HEADER_SIZE; i++) + Header[i] = 0; + Header[4] = MAJOR_VERSION; + Header[5] = MINOR_VERSION; + if (CurrentPacket == 1) + { + Header[6] = ((PacketType & 0xff00) >> 8); + Header[7] = (PacketType & 0x00ff); + } + else + { + Header[6] = ((PT_BLOB & 0xff00) >> 8); + Header[7] = (PT_BLOB & 0x00ff); + } + Header[8] = ((CurrentPacket & 0xff000000) >> 24); + Header[9] = ((CurrentPacket & 0x00ff0000) >> 16); + Header[10] = ((CurrentPacket & 0x0000ff00) >> 8); + Header[11] = (CurrentPacket & 0x000000ff); + + Header[12] = ((NumberOfPackets & 0xff000000) >> 24); + Header[13] = ((NumberOfPackets & 0x00ff0000) >> 16); + Header[14] = ((NumberOfPackets & 0x0000ff00) >> 8); + Header[15] = (NumberOfPackets & 0x000000ff); + + Header[16] = ((PayloadSize & 0xff00) >> 8); + Header[17] = (PayloadSize & 0x00ff); + + Header[18] = ((UniqueToken & 0xff000000) >> 24); + Header[19] = ((UniqueToken & 0x00ff0000) >> 16); + Header[20] = ((UniqueToken & 0x0000ff00) >> 8); + Header[21] = (UniqueToken & 0x000000ff); + } + + virtual void ConstructPayload() + { } +}; + +class CPacketHELO : public CPacket +{ + /************************************************************************/ + /* Payload format */ + /* %s - device name (max 128 chars) */ + /* %c - icontype ( 0=>NOICON, 1=>JPEG , 2=>PNG , 3=>GIF ) */ + /* %s - my port ( 0=>not listening ) */ + /* %d - reserved1 ( 0 ) */ + /* %d - reserved2 ( 0 ) */ + /* XX - imagedata ( can span multiple packets ) */ + /************************************************************************/ +private: + std::vector<char> m_DeviceName; + unsigned short m_IconType; + char *m_IconData; + unsigned short m_IconSize; +public: + virtual void ConstructPayload() + { + m_Payload.clear(); + + for (unsigned int i = 0; i < m_DeviceName.size(); i++) + m_Payload.push_back(m_DeviceName[i]); + + m_Payload.push_back('\0'); + + m_Payload.push_back(m_IconType); + + m_Payload.push_back(0); + m_Payload.push_back('\0'); + + for (int j = 0; j < 8; j++) + m_Payload.push_back(0); + + for (int ico = 0; ico < m_IconSize; ico++) + m_Payload.push_back(m_IconData[ico]); + } + + CPacketHELO(const char *DevName, unsigned short IconType, const char *IconFile = NULL) : CPacket() + { + m_PacketType = PT_HELO; + + unsigned int len = strlen(DevName); + for (unsigned int i = 0; i < len; i++) + m_DeviceName.push_back(DevName[i]); + + m_IconType = IconType; + + if (IconType == ICON_NONE || IconFile == NULL) + { + m_IconData = NULL; + m_IconSize = 0; + return; + } + + std::ifstream::pos_type size; + + std::ifstream file (IconFile, std::ios::in|std::ios::binary|std::ios::ate); + if (file.is_open()) + { + size = file.tellg(); + m_IconData = new char [size]; + file.seekg (0, std::ios::beg); + file.read (m_IconData, size); + file.close(); + m_IconSize = size; + } + else + { + m_IconType = ICON_NONE; + m_IconSize = 0; + } + } + + virtual ~CPacketHELO() + { + m_DeviceName.clear(); + if (m_IconData) + free(m_IconData); + } +}; + +class CPacketNOTIFICATION : public CPacket +{ + /************************************************************************/ + /* Payload format: */ + /* %s - caption */ + /* %s - message */ + /* %c - icontype ( 0=>NOICON, 1=>JPEG , 2=>PNG , 3=>GIF ) */ + /* %d - reserved ( 0 ) */ + /* XX - imagedata ( can span multiple packets ) */ + /************************************************************************/ +private: + std::vector<char> m_Title; + std::vector<char> m_Message; + unsigned short m_IconType; + char *m_IconData; + unsigned short m_IconSize; +public: + virtual void ConstructPayload() + { + m_Payload.clear(); + + for (unsigned int i = 0; i < m_Title.size(); i++) + m_Payload.push_back(m_Title[i]); + + m_Payload.push_back('\0'); + + for (unsigned int i = 0; i < m_Message.size(); i++) + m_Payload.push_back(m_Message[i]); + + m_Payload.push_back('\0'); + + m_Payload.push_back(m_IconType); + + for (int i = 0; i < 4; i++) + m_Payload.push_back(0); + + for (int ico = 0; ico < m_IconSize; ico++) + m_Payload.push_back(m_IconData[ico]); + } + + CPacketNOTIFICATION(const char *Title, const char *Message, unsigned short IconType, const char *IconFile = NULL) : CPacket() + { + m_PacketType = PT_NOTIFICATION; + m_IconData = NULL; + + unsigned int len = 0; + if (Title != NULL) + { + len = strlen(Title); + for (unsigned int i = 0; i < len; i++) + m_Title.push_back(Title[i]); + } + + if (Message != NULL) + { + len = strlen(Message); + for (unsigned int i = 0; i < len; i++) + m_Message.push_back(Message[i]); + } + m_IconType = IconType; + + if (IconType == ICON_NONE || IconFile == NULL) + return; + + std::ifstream::pos_type size; + + std::ifstream file (IconFile, std::ios::in|std::ios::binary|std::ios::ate); + if (file.is_open()) + { + size = file.tellg(); + m_IconData = new char [size]; + file.seekg (0, std::ios::beg); + file.read (m_IconData, size); + file.close(); + m_IconSize = size; + } + else + { + m_IconType = ICON_NONE; + m_IconSize = 0; + } + } + + virtual ~CPacketNOTIFICATION() + { + m_Title.clear(); + m_Message.clear(); + if (m_IconData) + free(m_IconData); + } +}; + +class CPacketBUTTON : public CPacket +{ + /************************************************************************/ + /* Payload format */ + /* %i - button code */ + /* %i - flags 0x01 => use button map/name instead of code */ + /* 0x02 => btn down */ + /* 0x04 => btn up */ + /* 0x08 => use amount */ + /* 0x10 => queue event */ + /* 0x20 => do not repeat */ + /* 0x40 => virtual key */ + /* 0x40 => axis key */ + /* %i - amount ( 0 => 65k maps to -1 => 1 ) */ + /* %s - device map (case sensitive and required if flags & 0x01) */ + /* "KB" - Standard keyboard map */ + /* "XG" - Xbox Gamepad */ + /* "R1" - Xbox Remote */ + /* "R2" - Xbox Universal Remote */ + /* "LI:devicename" - valid LIRC device map where 'devicename' */ + /* is the actual name of the LIRC device */ + /* "JS<num>:joyname" - valid Joystick device map where */ + /* 'joyname' is the name specified in */ + /* the keymap. JS only supports button code */ + /* and not button name currently (!0x01). */ + /* %s - button name (required if flags & 0x01) */ + /************************************************************************/ +private: + std::vector<char> m_DeviceMap; + std::vector<char> m_Button; + unsigned short m_ButtonCode; + unsigned short m_Amount; + unsigned short m_Flags; +public: + virtual void ConstructPayload() + { + m_Payload.clear(); + + if (m_Button.size() != 0) + { + if (!(m_Flags & BTN_USE_NAME)) // If the BTN_USE_NAME isn't flagged for some reason + m_Flags |= BTN_USE_NAME; + m_ButtonCode = 0; + } + else + m_Button.clear(); + + if (m_Amount > 0) + { + if (!(m_Flags & BTN_USE_AMOUNT)) + m_Flags |= BTN_USE_AMOUNT; + } + if (!((m_Flags & BTN_DOWN) || (m_Flags & BTN_UP))) //If none of them are tagged. + m_Flags |= BTN_DOWN; + + m_Payload.push_back(((m_ButtonCode & 0xff00) >> 8)); + m_Payload.push_back( (m_ButtonCode & 0x00ff)); + + m_Payload.push_back(((m_Flags & 0xff00) >> 8) ); + m_Payload.push_back( (m_Flags & 0x00ff)); + + m_Payload.push_back(((m_Amount & 0xff00) >> 8) ); + m_Payload.push_back( (m_Amount & 0x00ff)); + + + for (unsigned int i = 0; i < m_DeviceMap.size(); i++) + m_Payload.push_back(m_DeviceMap[i]); + + m_Payload.push_back('\0'); + + for (unsigned int i = 0; i < m_Button.size(); i++) + m_Payload.push_back(m_Button[i]); + + m_Payload.push_back('\0'); + } + + CPacketBUTTON(const char *Button, const char *DeviceMap, unsigned short Flags, unsigned short Amount = 0) : CPacket() + { + m_PacketType = PT_BUTTON; + m_Flags = Flags; + m_ButtonCode = 0; + m_Amount = Amount; + + unsigned int len = strlen(DeviceMap); + for (unsigned int i = 0; i < len; i++) + m_DeviceMap.push_back(DeviceMap[i]); + + len = strlen(Button); + for (unsigned int i = 0; i < len; i++) + m_Button.push_back(Button[i]); + } + + CPacketBUTTON(unsigned short ButtonCode, const char *DeviceMap, unsigned short Flags, unsigned short Amount = 0) : CPacket() + { + m_PacketType = PT_BUTTON; + m_Flags = Flags; + m_ButtonCode = ButtonCode; + m_Amount = Amount; + + unsigned int len = strlen(DeviceMap); + for (unsigned int i = 0; i < len; i++) + m_DeviceMap.push_back(DeviceMap[i]); + } + + CPacketBUTTON(unsigned short ButtonCode, unsigned short Flags, unsigned short Amount = 0) : CPacket() + { + m_PacketType = PT_BUTTON; + m_Flags = Flags; + m_ButtonCode = ButtonCode; + m_Amount = Amount; + } + + // Used to send a release event + CPacketBUTTON() : CPacket() + { + m_PacketType = PT_BUTTON; + m_Flags = BTN_UP; + m_Amount = 0; + m_ButtonCode = 0; + } + + virtual ~CPacketBUTTON() + { + m_DeviceMap.clear(); + m_Button.clear(); + } + + inline unsigned short GetFlags() { return m_Flags; } + inline unsigned short GetButtonCode() { return m_ButtonCode; } +}; + +class CPacketPING : public CPacket +{ + /************************************************************************/ + /* no payload */ + /************************************************************************/ +public: + CPacketPING() : CPacket() + { + m_PacketType = PT_PING; + } + virtual ~CPacketPING() + { } +}; + +class CPacketBYE : public CPacket +{ + /************************************************************************/ + /* no payload */ + /************************************************************************/ +public: + CPacketBYE() : CPacket() + { + m_PacketType = PT_BYE; + } + virtual ~CPacketBYE() + { } +}; + +class CPacketMOUSE : public CPacket +{ + /************************************************************************/ + /* Payload format */ + /* %c - flags */ + /* - 0x01 absolute position */ + /* %i - mousex (0-65535 => maps to screen width) */ + /* %i - mousey (0-65535 => maps to screen height) */ + /************************************************************************/ +private: + unsigned short m_X; + unsigned short m_Y; + unsigned char m_Flag; +public: + CPacketMOUSE(int X, int Y, unsigned char Flag = MS_ABSOLUTE) + { + m_PacketType = PT_MOUSE; + m_Flag = Flag; + m_X = X; + m_Y = Y; + } + + virtual void ConstructPayload() + { + m_Payload.clear(); + + m_Payload.push_back(m_Flag); + + m_Payload.push_back(((m_X & 0xff00) >> 8)); + m_Payload.push_back( (m_X & 0x00ff)); + + m_Payload.push_back(((m_Y & 0xff00) >> 8)); + m_Payload.push_back( (m_Y & 0x00ff)); + } + + virtual ~CPacketMOUSE() + { } +}; + +class CPacketLOG : public CPacket +{ + /************************************************************************/ + /* Payload format */ + /* %c - log type */ + /* %s - message */ + /************************************************************************/ +private: + std::vector<char> m_Message; + unsigned char m_LogLevel; + bool m_AutoPrintf; +public: + CPacketLOG(int LogLevel, const char *Message, bool AutoPrintf = true) + { + m_PacketType = PT_LOG; + + unsigned int len = strlen(Message); + for (unsigned int i = 0; i < len; i++) + m_Message.push_back(Message[i]); + + m_LogLevel = LogLevel; + m_AutoPrintf = AutoPrintf; + } + + virtual void ConstructPayload() + { + m_Payload.clear(); + + m_Payload.push_back( (m_LogLevel & 0x00ff) ); + + if (m_AutoPrintf) + { + char* str=&m_Message[0]; + printf("%s\n", str); + } + for (unsigned int i = 0; i < m_Message.size(); i++) + m_Payload.push_back(m_Message[i]); + + m_Payload.push_back('\0'); + } + + virtual ~CPacketLOG() + { } +}; + +class CPacketACTION : public CPacket +{ + /************************************************************************/ + /* Payload format */ + /* %c - action type */ + /* %s - action message */ + /************************************************************************/ +private: + unsigned char m_ActionType; + std::vector<char> m_Action; +public: + CPacketACTION(const char *Action, unsigned char ActionType = ACTION_EXECBUILTIN) + { + m_PacketType = PT_ACTION; + + m_ActionType = ActionType; + unsigned int len = strlen(Action); + for (unsigned int i = 0; i < len; i++) + m_Action.push_back(Action[i]); + } + + virtual void ConstructPayload() + { + m_Payload.clear(); + + m_Payload.push_back(m_ActionType); + for (unsigned int i = 0; i < m_Action.size(); i++) + m_Payload.push_back(m_Action[i]); + + m_Payload.push_back('\0'); + } + + virtual ~CPacketACTION() + { } +}; + +class CXBMCClient +{ +private: + CAddress m_Addr; + int m_Socket; + unsigned int m_UID; +public: + CXBMCClient(const char *IP = "127.0.0.1", int Port = 9777, int Socket = -1, unsigned int UID = 0) + { + m_Addr = CAddress(IP, Port); + if (Socket == -1) + m_Socket = socket(AF_INET, SOCK_DGRAM, 0); + else + m_Socket = Socket; + + if (UID) + m_UID = UID; + else + m_UID = XBMCClientUtils::GetUniqueIdentifier(); + } + + void SendNOTIFICATION(const char *Title, const char *Message, unsigned short IconType, const char *IconFile = NULL) + { + if (m_Socket < 0) + return; + + CPacketNOTIFICATION notification(Title, Message, IconType, IconFile); + notification.Send(m_Socket, m_Addr, m_UID); + } + + void SendHELO(const char *DevName, unsigned short IconType, const char *IconFile = NULL) + { + if (m_Socket < 0) + return; + + CPacketHELO helo(DevName, IconType, IconFile); + helo.Send(m_Socket, m_Addr, m_UID); + } + + void SendButton(const char *Button, const char *DeviceMap, unsigned short Flags, unsigned short Amount = 0) + { + if (m_Socket < 0) + return; + + CPacketBUTTON button(Button, DeviceMap, Flags, Amount); + button.Send(m_Socket, m_Addr, m_UID); + } + + void SendButton(unsigned short ButtonCode, const char *DeviceMap, unsigned short Flags, unsigned short Amount = 0) + { + if (m_Socket < 0) + return; + + CPacketBUTTON button(ButtonCode, DeviceMap, Flags, Amount); + button.Send(m_Socket, m_Addr, m_UID); + } + + void SendButton(unsigned short ButtonCode, unsigned Flags, unsigned short Amount = 0) + { + if (m_Socket < 0) + return; + + CPacketBUTTON button(ButtonCode, Flags, Amount); + button.Send(m_Socket, m_Addr, m_UID); + } + + void SendMOUSE(int X, int Y, unsigned char Flag = MS_ABSOLUTE) + { + if (m_Socket < 0) + return; + + CPacketMOUSE mouse(X, Y, Flag); + mouse.Send(m_Socket, m_Addr, m_UID); + } + + void SendLOG(int LogLevel, const char *Message, bool AutoPrintf = true) + { + if (m_Socket < 0) + return; + + CPacketLOG log(LogLevel, Message, AutoPrintf); + log.Send(m_Socket, m_Addr, m_UID); + } + + void SendACTION(const char *ActionMessage, int ActionType = ACTION_EXECBUILTIN) + { + if (m_Socket < 0) + return; + + CPacketACTION action(ActionMessage, ActionType); + action.Send(m_Socket, m_Addr, m_UID); + } +}; + +#endif diff --git a/tools/EventClients/lib/java/build.xml b/tools/EventClients/lib/java/build.xml new file mode 100755 index 0000000000..fe25bde90a --- /dev/null +++ b/tools/EventClients/lib/java/build.xml @@ -0,0 +1,72 @@ +<project default="jar" name="JXBMCEventClient"> + <description description="JXBMCEventClient"/> + <target name="init"> + <tstamp/> + <property name="srcdir" value="${basedir}/src"/> + <property name="classdir" value="${basedir}/classes"/> + <property name="apidir" value="${basedir}/doc/api"/> + <property name="libdir" value="/usr/local/share/java/classes"/> + <property name="projectname" value="JXBMCEventClient"/> + <property name="jarfile" value="${projectname}.jar"/> + <property name="distdir" value="${basedir}/../dist"/> + <fileset id="eventclient.files" dir="${classdir}" includes="**/*"/> + <path id="classpath"> + <pathelement location="${libdir}/junit.jar" /> + </path> + </target> + + + <target name="jar" depends="compile"> + <jar jarfile="${jarfile}" index="true"> + <fileset refid="eventclient.files"/> + </jar> + </target> + + <target name="dist" depends="jar, javadoc"> + <fail unless="version">use: ant dist -Dversion=x.x</fail> + <mkdir dir="${distdir}"/> + <copy todir="${distdir}/doc"> + <fileset dir="${apidir}"/> + </copy> + <property name="filename" value="${projectname}-${version}"/> + <copy file="${jarfile}" tofile="${distdir}/${projectname}-${version}.jar" /> + <tar destfile="${distdir}/${filename}.tar.gz" compression="gzip"> + <tarfileset dir="${basedir}" prefix="${filename}"> + <exclude name="classes/**"/> + <exclude name="**/CVS"/> + <exclude name="**/.*"/> + <exclude name="${jarfile}"/> + <exclude name="doc/**"/> + </tarfileset> + </tar> + </target> + + <target name="compile" depends="init" description="Compiles all classes"> + <mkdir dir="${classdir}"/> + <javac debug="yes" deprecation="true" destdir="${classdir}" srcdir="${srcdir}" classpathref="classpath"/> + </target> + + <target name="javadoc" depends="init"> + <mkdir dir="${apidir}"/> + <javadoc + packagenames="org.xbmc.eventclient.*" + sourcepath="${srcdir}" + defaultexcludes="yes" + destdir="${apidir}" + author="true" + version="true" + use="true" + private="false" + windowtitle="${projectname} API" + classpathref="classpath"> + <link href="http://java.sun.com/j2se/1.4.2/docs/api/"/> + </javadoc> + </target> + + <target name="clean" depends="init" description="cleans all classes and jars"> + <delete dir="${classdir}"/> + <delete dir="${apidir}"/> + <delete file="${jarfile}"/> + </target> + +</project> diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/Packet.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/Packet.java new file mode 100755 index 0000000000..4904d5c9b1 --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/Packet.java @@ -0,0 +1,271 @@ +package org.xbmc.eventclient; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; + +/** + * XBMC Event Client Class + * <p> + * Implementation of XBMC's UDP based input system. + * A set of classes that abstract the various packets that the event server + * currently supports. In addition, there's also a class, XBMCClient, that + * provides functions that sends the various packets. Use XBMCClient if you + * don't need complete control over packet structure. + * </p> + * <p> + * The basic workflow involves: + * <ol> + * <li>Send a HELO packet</li> + * <li>Send x number of valid packets</li> + * <li>Send a BYE packet</li> + * </ol> + * </p> + * <p> + * IMPORTANT NOTE ABOUT TIMEOUTS: + * A client is considered to be timed out if XBMC doesn't received a packet + * at least once every 60 seconds. To "ping" XBMC with an empty packet use + * PacketPING or XBMCClient.ping(). See the documentation for details. + * </p> + * <p> + * Base class that implements a single event packet. + * - Generic packet structure (maximum 1024 bytes per packet) + * - Header is 32 bytes long, so 992 bytes available for payload + * - large payloads can be split into multiple packets using H4 and H5 + * H5 should contain total no. of packets in such a case + * - H6 contains length of P1, which is limited to 992 bytes + * - if H5 is 0 or 1, then H4 will be ignored (single packet msg) + * - H7 must be set to zeros for now + * </p> + * <pre> + * ----------------------------- + * | -H1 Signature ("XBMC") | - 4 x CHAR 4B + * | -H2 Version (eg. 2.0) | - 2 x UNSIGNED CHAR 2B + * | -H3 PacketType | - 1 x UNSIGNED SHORT 2B + * | -H4 Sequence number | - 1 x UNSIGNED LONG 4B + * | -H5 No. of packets in msg | - 1 x UNSIGNED LONG 4B + * | -H6 Payloadsize of packet | - 1 x UNSIGNED SHORT 2B + * | -H7 Client's unique token | - 1 x UNSIGNED LONG 4B + * | -H8 Reserved | - 10 x UNSIGNED CHAR 10B + * |---------------------------| + * | -P1 payload | - + * ----------------------------- + * </pre> + * @author Stefan Agner + * + */ +public abstract class Packet { + + private byte[] sig; + private byte[] payload = new byte[0]; + private byte minver; + private byte majver; + + private short packettype; + + + private final static short MAX_PACKET_SIZE = 1024; + private final static short HEADER_SIZE = 32; + private final static short MAX_PAYLOAD_SIZE = MAX_PACKET_SIZE - HEADER_SIZE; + + protected final static byte PT_HELO = 0x01; + protected final static byte PT_BYE = 0x02; + protected final static byte PT_BUTTON = 0x03; + protected final static byte PT_MOUSE = 0x04; + protected final static byte PT_PING = 0x05; + protected final static byte PT_BROADCAST = 0x06; + protected final static byte PT_NOTIFICATION = 0x07; + protected final static byte PT_BLOB = 0x08; + protected final static byte PT_LOG = 0x09; + protected final static byte PT_ACTION = 0x0A; + protected final static byte PT_DEBUG = (byte)0xFF; + + public final static byte ICON_NONE = 0x00; + public final static byte ICON_JPEG = 0x01; + public final static byte ICON_PNG = 0x02; + public final static byte ICON_GIF = 0x03; + + private static int uid = (int)(Math.random()*Integer.MAX_VALUE); + + /** + * This is an Abstract class and cannot be instanced. Please use one of the Packet implementation Classes + * (PacketXXX). + * + * Implements an XBMC Event Client Packet. Type is to be specified at creation time, Payload can be added + * with the various appendPayload methods. Packet can be sent through UDP-Socket with method "send". + * @param packettype Type of Packet (PT_XXX) + */ + protected Packet(short packettype) + { + sig = new byte[] {'X', 'B', 'M', 'C' }; + minver = 0; + majver = 2; + this.packettype = packettype; + } + + /** + * Appends a String to the payload (terminated with 0x00) + * @param payload Payload as String + */ + protected void appendPayload(String payload) + { + byte[] payloadarr = payload.getBytes(); + int oldpayloadsize = this.payload.length; + byte[] oldpayload = this.payload; + this.payload = new byte[oldpayloadsize+payloadarr.length+1]; // Create new Array with more place (+1 for string terminator) + System.arraycopy(oldpayload, 0, this.payload, 0, oldpayloadsize); + System.arraycopy(payloadarr, 0, this.payload, oldpayloadsize, payloadarr.length); + } + + /** + * Appends a single Byte to the payload + * @param payload Payload + */ + protected void appendPayload(byte payload) + { + appendPayload(new byte[] { payload }); + } + + /** + * Appends a Byte-Array to the payload + * @param payloadarr Payload + */ + protected void appendPayload(byte[] payloadarr) + { + int oldpayloadsize = this.payload.length; + byte[] oldpayload = this.payload; + this.payload = new byte[oldpayloadsize+payloadarr.length]; + System.arraycopy(oldpayload, 0, this.payload, 0, oldpayloadsize); + System.arraycopy(payloadarr, 0, this.payload, oldpayloadsize, payloadarr.length); + } + + /** + * Appends an integer to the payload + * @param i Payload + */ + protected void appendPayload(int i) { + appendPayload(intToByteArray(i)); + } + + /** + * Appends a short to the payload + * @param s Payload + */ + protected void appendPayload(short s) { + appendPayload(shortToByteArray(s)); + } + + /** + * Get Number of Packets which will be sent with current Payload... + * @return Number of Packets + */ + public int getNumPackets() + { + return (int)((payload.length + (MAX_PAYLOAD_SIZE - 1)) / MAX_PAYLOAD_SIZE); + } + + /** + * Get Header for a specific Packet in this sequence... + * @param seq Current sequence number + * @param maxseq Maximal sequence number + * @param actpayloadsize Payloadsize of this packet + * @return Byte-Array with Header information (currently 32-Byte long, see HEADER_SIZE) + */ + private byte[] getHeader(int seq, int maxseq, short actpayloadsize) + { + byte[] header = new byte[HEADER_SIZE]; + System.arraycopy(sig, 0, header, 0, 4); + header[4] = majver; + header[5] = minver; + byte[] packettypearr = shortToByteArray(this.packettype); + System.arraycopy(packettypearr, 0, header, 6, 2); + byte[] seqarr = intToByteArray(seq); + System.arraycopy(seqarr, 0, header, 8, 4); + byte[] maxseqarr = intToByteArray(maxseq); + System.arraycopy(maxseqarr, 0, header, 12, 4); + byte[] payloadsize = shortToByteArray(actpayloadsize); + System.arraycopy(payloadsize, 0, header, 16, 2); + byte[] uid = intToByteArray(Packet.uid); + System.arraycopy(uid, 0, header, 18, 4); + byte[] reserved = new byte[10]; + System.arraycopy(reserved, 0, header, 22, 10); + + return header; + } + + /** + * Generates the whole UDP-Message with Header and Payload of a specific Packet in sequence + * @param seq Current sequence number + * @return Byte-Array with UDP-Message + */ + private byte[] getUDPMessage(int seq) + { + int maxseq = (int)((payload.length + (MAX_PAYLOAD_SIZE - 1)) / MAX_PAYLOAD_SIZE); + if(seq > maxseq) + return null; + + short actpayloadsize; + + if(seq == maxseq) + actpayloadsize = (short)(payload.length%MAX_PAYLOAD_SIZE); + + else + actpayloadsize = (short)MAX_PAYLOAD_SIZE; + + byte[] pack = new byte[HEADER_SIZE+actpayloadsize]; + + System.arraycopy(getHeader(seq, maxseq, actpayloadsize), 0, pack, 0, HEADER_SIZE); + System.arraycopy(payload, (seq-1)*MAX_PAYLOAD_SIZE, pack, HEADER_SIZE, actpayloadsize); + + return pack; + } + + /** + * Sends this packet to the EventServer + * @param adr Address of the EventServer + * @param port Port of the EventServer + * @throws IOException + */ + public void send(InetAddress adr, int port) throws IOException + { + int maxseq = getNumPackets(); + DatagramSocket s = new DatagramSocket(); + + // For each Packet in Sequence... + for(int seq=1;seq<=maxseq;seq++) + { + // Get Message and send them... + byte[] pack = getUDPMessage(seq); + DatagramPacket p = new DatagramPacket(pack, pack.length); + p.setAddress(adr); + p.setPort(port); + s.send(p); + } + } + + /** + * Helper Method to convert an integer to a Byte array + * @param value + * @return Byte-Array + */ + private static final byte[] intToByteArray(int value) { + return new byte[] { + (byte)(value >>> 24), + (byte)(value >>> 16), + (byte)(value >>> 8), + (byte)value}; + } + + /** + * Helper Method to convert an short to a Byte array + * @param value + * @return Byte-Array + */ + private static final byte[] shortToByteArray(short value) { + return new byte[] { + (byte)(value >>> 8), + (byte)value}; + } + + +} diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketACTION.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketACTION.java new file mode 100755 index 0000000000..ae633c783a --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketACTION.java @@ -0,0 +1,43 @@ +package org.xbmc.eventclient; +/** + * XBMC Event Client Class + * + * An ACTION packet tells XBMC to do the action specified, based on the type it knows were it needs to be sent. + * The idea is that this will be as in scripting/skining and keymapping, just triggered from afar. + * @author Stefan Agner + * + */ +public class PacketACTION extends Packet { + + public final static byte ACTION_EXECBUILTIN = 0x01; + public final static byte ACTION_BUTTON = 0x02; + + + /** + * An ACTION packet tells XBMC to do the action specified, based on the type it knows were it needs to be sent. + * @param actionmessage Actionmessage (as in scripting/skinning) + */ + public PacketACTION(String actionmessage) + { + super(PT_ACTION); + byte actiontype = ACTION_EXECBUILTIN; + appendPayload(actionmessage, actiontype); + } + + /** + * An ACTION packet tells XBMC to do the action specified, based on the type it knows were it needs to be sent. + * @param actionmessage Actionmessage (as in scripting/skinning) + * @param actiontype Actiontype (ACTION_EXECBUILTIN or ACTION_BUTTON) + */ + public PacketACTION(String actionmessage, byte actiontype) + { + super(PT_ACTION); + appendPayload(actionmessage, actiontype); + } + + private void appendPayload(String actionmessage, byte actiontype) + { + appendPayload(actiontype); + appendPayload(actionmessage); + } +} diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketBUTTON.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketBUTTON.java new file mode 100755 index 0000000000..cc0ff35efb --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketBUTTON.java @@ -0,0 +1,139 @@ +package org.xbmc.eventclient; +/** + * XBMC Event Client Class + * + * A button packet send a key press or release event to XBMC + * @author Stefan Agner + * + */ +public class PacketBUTTON extends Packet { + + protected final static byte BT_USE_NAME = 0x01; + protected final static byte BT_DOWN = 0x02; + protected final static byte BT_UP = 0x04; + protected final static byte BT_USE_AMOUNT = 0x08; + protected final static byte BT_QUEUE = 0x10; + protected final static byte BT_NO_REPEAT = 0x20; + protected final static byte BT_VKEY = 0x40; + protected final static byte BT_AXIS = (byte)0x80; + protected final static byte BT_AXISSINGLE = (byte)0x100; + + /** + * A button packet send a key press or release event to XBMC + * @param code raw button code (default: 0) + * @param repeat this key press should repeat until released (default: 1) + * Note that queued pressed cannot repeat. + * @param down if this is 1, it implies a press event, 0 implies a release + * event. (default: 1) + * @param queue a queued key press means that the button event is + * executed just once after which the next key press is processed. + * It can be used for macros. Currently there is no support for + * time delays between queued presses. (default: 0) + * @param amount unimplemented for now; in the future it will be used for + * specifying magnitude of analog key press events + * @param axis + */ + public PacketBUTTON(short code, boolean repeat, boolean down, boolean queue, short amount, byte axis) + { + super(PT_BUTTON); + String map_name = ""; + String button_name = ""; + short flags = 0; + appendPayload(code, map_name, button_name, repeat, down, queue, amount, axis, flags); + } + + /** + * A button packet send a key press or release event to XBMC + * @param map_name a combination of map_name and button_name refers to a + * mapping in the user's Keymap.xml or Lircmap.xml. + * map_name can be one of the following: + * <ul> + * <li>"KB" => standard keyboard map ( <keyboard> section )</li> + * <li>"XG" => xbox gamepad map ( <gamepad> section )</li> + * <li>"R1" => xbox remote map ( <remote> section )</li> + * <li>"R2" => xbox universal remote map ( <universalremote> section )</li> + * <li>"LI:devicename" => LIRC remote map where 'devicename' is the + * actual device's name</li></ul> + * @param button_name a button name defined in the map specified in map_name. + * For example, if map_name is "KB" refering to the <keyboard> section in Keymap.xml + * then, valid button_names include "printscreen", "minus", "x", etc. + * @param repeat this key press should repeat until released (default: 1) + * Note that queued pressed cannot repeat. + * @param down if this is 1, it implies a press event, 0 implies a release + * event. (default: 1) + * @param queue a queued key press means that the button event is + * executed just once after which the next key press is processed. + * It can be used for macros. Currently there is no support for + * time delays between queued presses. (default: 0) + * @param amount unimplemented for now; in the future it will be used for + * specifying magnitude of analog key press events + * @param axis + */ + public PacketBUTTON(String map_name, String button_name, boolean repeat, boolean down, boolean queue, short amount, byte axis) + { + super(PT_BUTTON); + short code = 0; + short flags = BT_USE_NAME; + appendPayload(code, map_name, button_name, repeat, down, queue, amount, axis, flags); + } + + /** + * Appends Payload for a Button Packet (this method is used by the different Constructors of this Packet) + * @param code raw button code (default: 0) + * @param map_name a combination of map_name and button_name refers to a + * mapping in the user's Keymap.xml or Lircmap.xml. + * map_name can be one of the following: + * <ul> + * <li>"KB" => standard keyboard map ( <keyboard> section )</li> + * <li>"XG" => xbox gamepad map ( <gamepad> section )</li> + * <li>"R1" => xbox remote map ( <remote> section )</li> + * <li>"R2" => xbox universal remote map ( <universalremote> section )</li> + * <li>"LI:devicename" => LIRC remote map where 'devicename' is the + * actual device's name</li></ul> + * @param button_name a button name defined in the map specified in map_name. + * For example, if map_name is "KB" refering to the <keyboard> section in Keymap.xml + * then, valid button_names include "printscreen", "minus", "x", etc. + * @param repeat this key press should repeat until released (default: 1) + * Note that queued pressed cannot repeat. + * @param down if this is 1, it implies a press event, 0 implies a release + * event. (default: 1) + * @param queue a queued key press means that the button event is + * executed just once after which the next key press is processed. + * It can be used for macros. Currently there is no support for + * time delays between queued presses. (default: 0) + * @param amount unimplemented for now; in the future it will be used for + * specifying magnitude of analog key press events + * @param axis + * @param flags Packet specific flags + */ + private void appendPayload(short code, String map_name, String button_name, boolean repeat, boolean down, boolean queue, short amount, byte axis, short flags) + { + if(amount>0) + flags |= BT_USE_AMOUNT; + else + amount = 0; + + if(down) + flags |= BT_DOWN; + else + flags |= BT_UP; + + if(!repeat) + flags |= BT_NO_REPEAT; + + if(queue) + flags |= BT_QUEUE; + + if(axis == 1) + flags |= BT_AXISSINGLE; + else if (axis == 2) + flags |= BT_AXIS; + + + appendPayload(code); + appendPayload(flags); + appendPayload(amount); + appendPayload(map_name); + appendPayload(button_name); + } +} diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketBYE.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketBYE.java new file mode 100755 index 0000000000..8c831b1ac0 --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketBYE.java @@ -0,0 +1,19 @@ +package org.xbmc.eventclient; +/** + * XBMC Event Client Class + * + * A BYE packet terminates the connection to XBMC. + * @author Stefan Agner + * + */ +public class PacketBYE extends Packet +{ + + /** + * A BYE packet terminates the connection to XBMC. + */ + public PacketBYE() + { + super(PT_BYE); + } +} diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketHELO.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketHELO.java new file mode 100755 index 0000000000..f68ba46421 --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketHELO.java @@ -0,0 +1,46 @@ +package org.xbmc.eventclient; +import java.nio.charset.Charset; + +/** + * XBMC Event Client Class + * + * A HELO packet establishes a valid connection to XBMC. It is the + * first packet that should be sent. + * @author Stefan Agner + * + */ +public class PacketHELO extends Packet { + + + /** + * A HELO packet establishes a valid connection to XBMC. + * @param devicename Name of the device which connects to XBMC + */ + public PacketHELO(String devicename) + { + super(PT_HELO); + this.appendPayload(devicename); + this.appendPayload(ICON_NONE); + this.appendPayload((short)0); // port no + this.appendPayload(0); // reserved1 + this.appendPayload(0); // reserved2 + } + + /** + * A HELO packet establishes a valid connection to XBMC. + * @param devicename Name of the device which connects to XBMC + * @param iconType Type of the icon (Packet.ICON_PNG, Packet.ICON_JPEG or Packet.ICON_GIF) + * @param iconData The icon as a Byte-Array + */ + public PacketHELO(String devicename, byte iconType, byte[] iconData) + { + super(PT_HELO); + this.appendPayload(devicename); + this.appendPayload(iconType); + this.appendPayload((short)0); // port no + this.appendPayload(0); // reserved1 + this.appendPayload(0); // reserved2 + this.appendPayload(iconData); // reserved2 + } + +} diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketLOG.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketLOG.java new file mode 100755 index 0000000000..693ad71ea6 --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketLOG.java @@ -0,0 +1,30 @@ +package org.xbmc.eventclient; +/** + * XBMC Event Client Class + * + * A LOG packet tells XBMC to log the message to xbmc.log with the loglevel as specified. + * @author Stefan Agner + * + */ +public class PacketLOG extends Packet { + + /** + * A LOG packet tells XBMC to log the message to xbmc.log with the loglevel as specified. + * @param loglevel the loglevel, follows XBMC standard. + * <ul> + * <li>0 = DEBUG</li> + * <li>1 = INFO</li> + * <li>2 = NOTICE</li> + * <li>3 = WARNING</li> + * <li>4 = ERROR</li> + * <li>5 = SEVERE</li> + * </ul> + * @param logmessage the message to log + */ + public PacketLOG(byte loglevel, String logmessage) + { + super(PT_LOG); + appendPayload(loglevel); + appendPayload(logmessage); + } +} diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketMOUSE.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketMOUSE.java new file mode 100755 index 0000000000..d7755ef0cc --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketMOUSE.java @@ -0,0 +1,27 @@ +package org.xbmc.eventclient; +/** + * XBMC Event Client Class + * + * A MOUSE packets sets the mouse position in XBMC + * @author Stefan Agner + * + */ +public class PacketMOUSE extends Packet { + + protected final static byte MS_ABSOLUTE = 0x01; + + /** + * A MOUSE packets sets the mouse position in XBMC + * @param x horitontal position ranging from 0 to 65535 + * @param y vertical position ranging from 0 to 65535 + */ + public PacketMOUSE(int x, int y) + { + super(PT_MOUSE); + byte flags = 0; + flags |= MS_ABSOLUTE; + appendPayload(flags); + appendPayload((short)x); + appendPayload((short)y); + } +} diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketNOTIFICATION.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketNOTIFICATION.java new file mode 100755 index 0000000000..15a5ec8f27 --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketNOTIFICATION.java @@ -0,0 +1,53 @@ +package org.xbmc.eventclient; +/** + * XBMC Event Client Class + * + * This packet displays a notification window in XBMC. It can contain + * a caption, a message and an icon. + * @author Stefan Agner + * + */ +public class PacketNOTIFICATION extends Packet { + + /** + * This packet displays a notification window in XBMC. + * @param title Message title + * @param message The actual message + * @param iconType Type of the icon (Packet.ICON_PNG, Packet.ICON_JPEG or Packet.ICON_GIF) + * @param iconData The icon as a Byte-Array + */ + public PacketNOTIFICATION(String title, String message, byte iconType, byte[] iconData) + { + super(PT_NOTIFICATION); + appendPayload(title, message, iconType, iconData); + } + + /** + * This packet displays a notification window in XBMC. + * @param title Message title + * @param message The actual message + */ + public PacketNOTIFICATION(String title, String message) + { + super(PT_NOTIFICATION); + appendPayload(title, message, Packet.ICON_NONE, null); + } + + /** + * Appends the payload to the packet... + * @param title Message title + * @param message The actual message + * @param iconType Type of the icon (Packet.ICON_PNG, Packet.ICON_JPEG or Packet.ICON_GIF) + * @param iconData The icon as a Byte-Array + */ + private void appendPayload(String title, String message, byte iconType, byte[] iconData) + { + appendPayload(title); + appendPayload(message); + appendPayload(iconType); + appendPayload(0); // reserved + if(iconData!=null) + appendPayload(iconData); + + } +} diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketPING.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketPING.java new file mode 100755 index 0000000000..e405812f32 --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketPING.java @@ -0,0 +1,19 @@ +package org.xbmc.eventclient; +/** + * XBMC Event Client Class + * + * A PING packet tells XBMC that the client is still alive. All valid + * packets act as ping (not just this one). A client needs to ping + * XBMC at least once in 60 seconds or it will time + * @author Stefan Agner + * + */ +public class PacketPING extends Packet { + /** + * A PING packet tells XBMC that the client is still alive. + */ + public PacketPING() + { + super(PT_PING); + } +} diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/XBMCClient.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/XBMCClient.java new file mode 100755 index 0000000000..4a0f85fd02 --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/XBMCClient.java @@ -0,0 +1,300 @@ +package org.xbmc.eventclient; + +import java.io.FileInputStream; +import java.io.IOException; +import java.net.InetAddress; + +/** + * XBMC Event Client Class + * + * Implements an XBMC-Client. This class can be used to implement your own application which + * should act as a Input device for XBMC. Also starts a Ping-Thread, which tells the XBMC EventServer + * that the client is alive. Therefore if you close your application you SHOULD call stopClient()! + * @author Stefan Agner + * + */ +public class XBMCClient +{ + private boolean hasIcon = false; + private String deviceName; + private PingThread oPingThread; + private byte iconType = Packet.ICON_PNG; + private byte[] iconData; + private InetAddress hostAddress; + private int hostPort; + + /** + * Starts a XBMC EventClient. + * @param hostAddress Address of the Host running XBMC + * @param hostPort Port of the Host running XBMC (default 9777) + * @param deviceName Name of the Device + * @param iconFile Path to the Iconfile (PNG, JPEG or GIF) + * @throws IOException + */ + public XBMCClient(InetAddress hostAddress, int hostPort, String deviceName, String iconFile) throws IOException + { + byte iconType = Packet.ICON_PNG; + // Assume png as icon type + if(iconFile.toLowerCase().endsWith(".jpeg")) + iconType = Packet.ICON_JPEG; + if(iconFile.toLowerCase().endsWith(".jpg")) + iconType = Packet.ICON_JPEG; + if(iconFile.toLowerCase().endsWith(".gif")) + iconType = Packet.ICON_GIF; + + // Read the icon file to the byte array... + FileInputStream iconFileStream = new FileInputStream(iconFile); + byte[] iconData = new byte[iconFileStream.available()]; + iconFileStream.read(iconData); + + hasIcon = true; + + // Call start-Method... + startClient(hostAddress, hostPort, deviceName, iconType, iconData); + } + + + /** + * Starts a XBMC EventClient. + * @param hostAddress Address of the Host running XBMC + * @param hostPort Port of the Host running XBMC (default 9777) + * @param deviceName Name of the Device + * @param iconType Type of the icon file (see Packet.ICON_PNG, Packet.ICON_JPEG or Packet.ICON_GIF) + * @param iconData The icon itself as a Byte-Array + * @throws IOException + */ + public XBMCClient(InetAddress hostAddress, int hostPort, String deviceName, byte iconType, byte[] iconData) throws IOException + { + hasIcon = true; + startClient(hostAddress, hostPort, deviceName, iconType, iconData); + } + + /** + * Starts a XBMC EventClient without an icon. + * @param hostAddress Address of the Host running XBMC + * @param hostPort Port of the Host running XBMC (default 9777) + * @param deviceName Name of the Device + * @throws IOException + */ + public XBMCClient(InetAddress hostAddress, int hostPort, String deviceName) throws IOException + { + hasIcon = false; + byte iconType = Packet.ICON_NONE; + byte[] iconData = null; + startClient(hostAddress, hostPort, deviceName, iconType, iconData); + } + + + /** + * Starts a XBMC EventClient. + * @param hostAddress Address of the Host running XBMC + * @param hostPort Port of the Host running XBMC (default 9777) + * @param deviceName Name of the Device + * @param iconType Type of the icon file (see Packet.ICON_PNG, Packet.ICON_JPEG or Packet.ICON_GIF) + * @param iconData The icon itself as a Byte-Array + * @throws IOException + */ + private void startClient(InetAddress hostAddress, int hostPort, String deviceName, byte iconType, byte[] iconData) throws IOException + { + // Save host address and port + this.hostAddress = hostAddress; + this.hostPort = hostPort; + this.deviceName = deviceName; + + this.iconType = iconType; + this.iconData = iconData; + + // Send Hello Packet... + PacketHELO p; + if(hasIcon) + p = new PacketHELO(deviceName, iconType, iconData); + else + p = new PacketHELO(deviceName); + + p.send(hostAddress, hostPort); + + // Start Thread (for Ping packets...) + oPingThread = new PingThread(hostAddress, hostPort, 20000); + oPingThread.start(); + } + + /** + * Stops the XBMC EventClient (especially the Ping-Thread) + * @throws IOException + */ + public void stopClient() throws IOException + { + // Stop Ping-Thread... + oPingThread.giveup(); + oPingThread.interrupt(); + + PacketBYE p = new PacketBYE(); + p.send(hostAddress, hostPort); + } + + + /** + * Displays a notification window in XBMC. + * @param title Message title + * @param message The actual message + */ + public void sendNotification(String title, String message) throws IOException + { + PacketNOTIFICATION p; + if(hasIcon) + p = new PacketNOTIFICATION(title, message, iconType, iconData); + else + p = new PacketNOTIFICATION(title, message); + p.send(hostAddress, hostPort); + } + + /** + * Sends a Button event + * @param code raw button code (default: 0) + * @param repeat this key press should repeat until released (default: 1) + * Note that queued pressed cannot repeat. + * @param down if this is 1, it implies a press event, 0 implies a release + * event. (default: 1) + * @param queue a queued key press means that the button event is + * executed just once after which the next key press is processed. + * It can be used for macros. Currently there is no support for + * time delays between queued presses. (default: 0) + * @param amount unimplemented for now; in the future it will be used for + * specifying magnitude of analog key press events + * @param axis + */ + public void sendButton(short code, boolean repeat, boolean down, boolean queue, short amount, byte axis) throws IOException + { + PacketBUTTON p = new PacketBUTTON(code, repeat, down, queue, amount, axis); + p.send(hostAddress, hostPort); + } + + /** + * Sends a Button event + * @param map_name a combination of map_name and button_name refers to a + * mapping in the user's Keymap.xml or Lircmap.xml. + * map_name can be one of the following: + * <ul> + * <li>"KB" => standard keyboard map ( <keyboard> section )</li> + * <li>"XG" => xbox gamepad map ( <gamepad> section )</li> + * <li>"R1" => xbox remote map ( <remote> section )</li> + * <li>"R2" => xbox universal remote map ( <universalremote> section )</li> + * <li>"LI:devicename" => LIRC remote map where 'devicename' is the + * actual device's name</li></ul> + * @param button_name a button name defined in the map specified in map_name. + * For example, if map_name is "KB" refering to the <keyboard> section in Keymap.xml + * then, valid button_names include "printscreen", "minus", "x", etc. + * @param repeat this key press should repeat until released (default: 1) + * Note that queued pressed cannot repeat. + * @param down if this is 1, it implies a press event, 0 implies a release + * event. (default: 1) + * @param queue a queued key press means that the button event is + * executed just once after which the next key press is processed. + * It can be used for macros. Currently there is no support for + * time delays between queued presses. (default: 0) + * @param amount unimplemented for now; in the future it will be used for + * specifying magnitude of analog key press events + * @param axis + */ + public void sendButton(String map_name, String button_name, boolean repeat, boolean down, boolean queue, short amount, byte axis) throws IOException + { + PacketBUTTON p = new PacketBUTTON(map_name, button_name, repeat, down, queue, amount, axis); + p.send(hostAddress, hostPort); + } + + /** + * Sets the mouse position in XBMC + * @param x horitontal position ranging from 0 to 65535 + * @param y vertical position ranging from 0 to 65535 + */ + public void sendMouse(int x, int y) throws IOException + { + PacketMOUSE p = new PacketMOUSE(x, y); + p.send(hostAddress, hostPort); + } + + /** + * Sends a ping to the XBMC EventServer + * @throws IOException + */ + public void ping() throws IOException + { + PacketPING p = new PacketPING(); + p.send(hostAddress, hostPort); + } + + /** + * Tells XBMC to log the message to xbmc.log with the loglevel as specified. + * @param loglevel the loglevel, follows XBMC standard. + * <ul> + * <li>0 = DEBUG</li> + * <li>1 = INFO</li> + * <li>2 = NOTICE</li> + * <li>3 = WARNING</li> + * <li>4 = ERROR</li> + * <li>5 = SEVERE</li> + * </ul> + * @param logmessage the message to log + */ + public void sendLog(byte loglevel, String logmessage) throws IOException + { + PacketLOG p = new PacketLOG(loglevel, logmessage); + p.send(hostAddress, hostPort); + } + + /** + * Tells XBMC to do the action specified, based on the type it knows were it needs to be sent. + * @param actionmessage Actionmessage (as in scripting/skinning) + */ + public void sendAction(String actionmessage) throws IOException + { + PacketACTION p = new PacketACTION(actionmessage); + p.send(hostAddress, hostPort); + } + + /** + * Implements a PingThread which tells XBMC EventServer that the Client is alive (this should + * be done at least every 60 seconds! + * @author Stefan Agner + * + */ + class PingThread extends Thread + { + private InetAddress hostAddress; + private int hostPort; + private int sleepTime; + private boolean giveup = false; + + public PingThread(InetAddress hostAddress, int hostPort, int sleepTime) + { + super("XBMC EventClient Ping-Thread"); + this.hostAddress = hostAddress; + this.hostPort = hostPort; + this.sleepTime = sleepTime; + } + + public void giveup() + { + giveup = true; + } + + public void run() + { + while(!giveup) + { + try { + PacketPING p = new PacketPING(); + p.send(hostAddress, hostPort); + } catch (IOException e) { + + e.printStackTrace(); + } + + try { + Thread.sleep(sleepTime); + } catch (InterruptedException e) { + } + } + } + } +} diff --git a/tools/EventClients/lib/python/__init__.py b/tools/EventClients/lib/python/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tools/EventClients/lib/python/__init__.py diff --git a/tools/EventClients/lib/python/bt/__init__.py b/tools/EventClients/lib/python/bt/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tools/EventClients/lib/python/bt/__init__.py diff --git a/tools/EventClients/lib/python/bt/bt.py b/tools/EventClients/lib/python/bt/bt.py new file mode 100644 index 0000000000..de4fd1bb65 --- /dev/null +++ b/tools/EventClients/lib/python/bt/bt.py @@ -0,0 +1,65 @@ +BLUEZ=0 + +try: + import bluetooth + BLUEZ=1 +except: + try: + import lightblue + except: + print "ERROR: You need to have either LightBlue or PyBluez installed\n"\ + " in order to use this program." + print "- PyBluez (Linux / Windows XP) http://org.csail.mit.edu/pybluez/" + print "- LightBlue (Mac OS X / Linux) http://lightblue.sourceforge.net/" + exit() + +def bt_create_socket(): + if BLUEZ: + sock = bluetooth.BluetoothSocket(bluetooth.L2CAP) + else: + sock = lightblue.socket(lightblue.L2CAP) + return sock + +def bt_create_rfcomm_socket(): + if BLUEZ: + sock = bluetooth.BluetoothSocket( bluetooth.RFCOMM ) + sock.bind(("",bluetooth.PORT_ANY)) + else: + sock = lightblue.socket(lightblue.RFCOMM) + sock.bind(("",0)) + return sock + +def bt_discover_devices(): + if BLUEZ: + nearby = bluetooth.discover_devices() + else: + nearby = lightblue.finddevices() + return nearby + +def bt_lookup_name(bdaddr): + if BLUEZ: + bname = bluetooth.lookup_name( bdaddr ) + else: + bname = bdaddr[1] + return bname + +def bt_lookup_addr(bdaddr): + if BLUEZ: + return bdaddr + else: + return bdaddr[0] + +def bt_advertise(name, uuid, socket): + if BLUEZ: + bluetooth.advertise_service( socket, name, + service_id = uuid, + service_classes = [ uuid, bluetooth.SERIAL_PORT_CLASS ], + profiles = [ bluetooth.SERIAL_PORT_PROFILE ] ) + else: + lightblue.advertise(name, socket, lightblue.RFCOMM) + +def bt_stop_advertising(socket): + if BLUEZ: + stop_advertising(socket) + else: + lightblue.stopadvertise(socket) diff --git a/tools/EventClients/lib/python/bt/hid.py b/tools/EventClients/lib/python/bt/hid.py new file mode 100644 index 0000000000..7017185dc4 --- /dev/null +++ b/tools/EventClients/lib/python/bt/hid.py @@ -0,0 +1,54 @@ +from bluetooth import * + +class HID: + def __init__(self, bdaddress=None): + self.cport = 0x11 # HID's control PSM + self.iport = 0x13 # HID' interrupt PSM + self.backlog = 1 + + self.address = "" + if bdaddress: + self.address = bdaddress + + # create the HID control socket + self.csock = BluetoothSocket( L2CAP ) + self.csock.bind((self.address, self.cport)) + set_l2cap_mtu(self.csock, 64) + self.csock.settimeout(2) + self.csock.listen(self.backlog) + + # create the HID interrupt socket + self.isock = BluetoothSocket( L2CAP ) + self.isock.bind((self.address, self.iport)) + set_l2cap_mtu(self.isock, 64) + self.isock.settimeout(2) + self.isock.listen(self.backlog) + + self.connected = False + + + def listen(self): + try: + (self.client_csock, self.caddress) = self.csock.accept() + print "Accepted Control connection from %s" % self.caddress[0] + (self.client_isock, self.iaddress) = self.isock.accept() + print "Accepted Interrupt connection from %s" % self.iaddress[0] + self.connected = True + return True + except Exception, e: + self.connected = False + return False + + + def get_control_socket(self): + if self.connected: + return (self.client_csock, self.caddress) + else: + return None + + + def get_interrupt_socket(self): + if self.connected: + return (self.client_isock, self.iaddress) + else: + return None diff --git a/tools/EventClients/lib/python/ps3/__init__.py b/tools/EventClients/lib/python/ps3/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tools/EventClients/lib/python/ps3/__init__.py diff --git a/tools/EventClients/lib/python/ps3/keymaps.py b/tools/EventClients/lib/python/ps3/keymaps.py new file mode 100644 index 0000000000..abe7dd6427 --- /dev/null +++ b/tools/EventClients/lib/python/ps3/keymaps.py @@ -0,0 +1,118 @@ +# PS3 Remote and Controller Keymaps + +keymap_remote = { + "16": 'power' ,#EJECT + "64": None ,#AUDIO + "65": None ,#ANGLE + "63": None ,#SUBTITLE + "0f": None ,#CLEAR + "28": None ,#TIME + + "00": 'one' ,#1 + "01": 'two' ,#2 + "02": 'three' ,#3 + "03": 'four' ,#4 + "04": 'five' ,#5 + "05": 'six' ,#6 + "06": 'seven' ,#7 + "07": 'eight' ,#8 + "08": 'nine' ,#9 + "09": 'zero' ,#0 + + "81": 'mytv' ,#RED + "82": 'mymusic' ,#GREEN + "80": 'mypictures' ,#BLUE + "83": 'myvideo' ,#YELLOW + + "70": 'display' ,#DISPLAY + "1a": None ,#TOP MENU + "40": 'menu' ,#POP UP/MENU + "0e": None ,#RETURN + + "5c": 'menu' ,#OPTIONS/TRIANGLE + "5d": 'back' ,#BACK/CIRCLE + "5e": 'info' ,#X + "5f": 'title' ,#VIEW/SQUARE + + "54": 'up' ,#UP + "55": 'right' ,#RIGHT + "56": 'down' ,#DOWN + "57": 'left' ,#LEFT + "0b": 'select' ,#ENTER + + "5a": 'volumeplus' ,#L1 + "58": 'volumeminus' ,#L2 + "51": 'Mute' ,#L3 + "5b": 'pageplus' ,#R1 + "59": 'pageminus' ,#R2 + "52": None ,#R3 + + "43": None ,#PLAYSTATION + "50": None ,#SELECT + "53": None ,#START + + "33": 'reverse' ,#<-SCAN + "34": 'forward' ,# SCAN-> + "30": 'skipminus' ,#PREV + "31": 'skipplus' ,#NEXT + "60": None ,#<-SLOW/STEP + "61": None ,# SLOW/STEP-> + "32": 'play' ,#PLAY + "38": 'stop' ,#STOP + "39": 'pause' ,#PAUSE + } + + +SX_SQUARE = 32768 +SX_X = 16384 +SX_CIRCLE = 8192 +SX_TRIANGLE = 4096 +SX_R1 = 2048 +SX_R2 = 512 +SX_R3 = 4 +SX_L1 = 1024 +SX_L2 = 256 +SX_L3 = 2 +SX_DUP = 16 +SX_DDOWN = 64 +SX_DLEFT = 128 +SX_DRIGHT = 32 +SX_SELECT = 1 +SX_START = 8 + +SX_LSTICK_X = 0 +SX_LSTICK_Y = 1 +SX_RSTICK_X = 2 +SX_RSTICK_Y = 3 + +# (map, key, amount index, axis) +keymap_sixaxis = { + SX_X : ('XG', 'A', 0, 0), + SX_CIRCLE : ('XG', 'B', 0, 0), + SX_SQUARE : ('XG', 'X', 0, 0), + SX_TRIANGLE : ('XG', 'Y', 0, 0), + + SX_DUP : ('XG', 'dpadup', 0, 0), + SX_DDOWN : ('XG', 'dpaddown', 0, 0), + SX_DLEFT : ('XG', 'dpadleft', 0, 0), + SX_DRIGHT : ('XG', 'dpadright', 0, 0), + + SX_START : ('XG', 'start', 0, 0), + SX_SELECT : ('XG', 'back', 0, 0), + + SX_R1 : ('XG', 'white', 0, 0), + SX_R2 : ('XG', 'rightanalogtrigger', 6, 1), + SX_L2 : ('XG', 'leftanalogtrigger', 5, 1), + SX_L1 : ('XG', 'black', 0, 0), + + SX_L3 : ('XG', 'leftthumbbutton', 0, 0), + SX_R3 : ('XG', 'rightthumbbutton', 0, 0), +} + +# (data index, left map, left action, right map, right action) +axismap_sixaxis = { + SX_LSTICK_X : ('XG', 'leftthumbstickleft' , 'leftthumbstickright'), + SX_LSTICK_Y : ('XG', 'leftthumbstickup' , 'leftthumbstickdown'), + SX_RSTICK_X : ('XG', 'rightthumbstickleft', 'rightthumbstickright'), + SX_RSTICK_Y : ('XG', 'rightthumbstickup' , 'rightthumbstickdown'), +} diff --git a/tools/EventClients/lib/python/ps3/sixaxis.py b/tools/EventClients/lib/python/ps3/sixaxis.py new file mode 100644 index 0000000000..15014b8ea0 --- /dev/null +++ b/tools/EventClients/lib/python/ps3/sixaxis.py @@ -0,0 +1,206 @@ +#!/usr/bin/python + +import time +import sys +import struct +import math +import binascii +from bluetooth import set_l2cap_mtu +from keymaps import keymap_sixaxis +from keymaps import axismap_sixaxis + +xval = 0 +yval = 0 +num_samples = 16 +sumx = [0] * num_samples +sumy = [0] * num_samples +sumr = [0] * num_samples +axis_amount = [0, 0, 0, 0] + +def normalize(val): + upperlimit = 65281 + lowerlimit = 2 + val_range = upperlimit - lowerlimit + offset = 10000 + + val = (val + val_range / 2) % val_range + upperlimit -= offset + lowerlimit += offset + + if val < lowerlimit: + val = lowerlimit + if val > upperlimit: + val = upperlimit + + val = ((float(val) - offset) / (float(upperlimit) - + lowerlimit)) * 65535.0 + if val <= 0: + val = 1 + return val + +def normalize_axis(val, deadzone): + + val = float(val) - 127.5 + val = val / 127.5 + + if abs(val) < deadzone: + return 0.0 + + if val > 0.0: + val = (val - deadzone) / (1.0 - deadzone) + else: + val = (val + deadzone) / (1.0 - deadzone) + + return 65536.0 * val + +def normalize_angle(val, valrange): + valrange *= 2 + + val = val / valrange + if val > 1.0: + val = 1.0 + if val < -1.0: + val = -1.0 + return (val + 0.5) * 65535.0 + +def initialize(control_sock, interrupt_sock): + # sixaxis needs this to enable it + # 0x53 => HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_FEATURE + control_sock.send("\x53\xf4\x42\x03\x00\x00") + time.sleep(0.25) + data = control_sock.recv(1) + + set_l2cap_mtu(control_sock, 64) + set_l2cap_mtu(interrupt_sock, 64) + + # This command will turn on the gyro and set the leds + # I wonder if turning on the gyro makes it draw more current?? + # it's probably a flag somewhere in the following command + + # HID Command: HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_OUTPUT + # HID Report:1 + bytes = [0x52, 0x1] + bytes.extend([0x00, 0x00, 0x00]) + bytes.extend([0xFF, 0x72]) + bytes.extend([0x00, 0x00, 0x00, 0x00]) + bytes.extend([0x02]) # 0x02 LED1, 0x04 LED2 ... 0x10 LED4 + # The following sections should set the blink frequncy of + # the leds on the controller, but i've not figured out how. + # These values where suggusted in a mailing list, but no explination + # for how they should be combined to the 5 bytes per led + #0xFF = 0.5Hz + #0x80 = 1Hz + #0x40 = 2Hz + bytes.extend([0xFF, 0x00, 0x01, 0x00, 0x01]) #LED4 [0xff, 0xff, 0x10, 0x10, 0x10] + bytes.extend([0xFF, 0x00, 0x01, 0x00, 0x01]) #LED3 [0xff, 0x40, 0x08, 0x10, 0x10] + bytes.extend([0xFF, 0x00, 0x01, 0x00, 0x01]) #LED2 [0xff, 0x00, 0x10, 0x30, 0x30] + bytes.extend([0xFF, 0x00, 0x01, 0x00, 0x01]) #LED1 [0xff, 0x00, 0x10, 0x40, 0x10] + bytes.extend([0x00, 0x00, 0x00, 0x00, 0x00]) + bytes.extend([0x00, 0x00, 0x00, 0x00, 0x00]) + + control_sock.send(struct.pack("42B", *bytes)) + time.sleep(0.25) + data = control_sock.recv(1) + + + return data + + +def read_input(isock): + return isock.recv(50) + + +def process_input(data, xbmc=None, mouse_enabled=0): + if len(data) < 3: + return (0, 0, 0) + + # make sure this is the correct report + if struct.unpack("BBB", data[0:3]) != (0xa1, 0x01, 0x00): + return (0, 0, 0) + + if len(data) >= 48: + v1 = struct.unpack("h", data[42:44]) + v2 = struct.unpack("h", data[44:46]) + v3 = struct.unpack("h", data[46:48]) + else: + v1 = [0,0] + v2 = [0,0] + v3 = [0,0] + + if len(data) >= 50: + v4 = struct.unpack("h", data[48:50]) + else: + v4 = [0,0] + + ax = float(v1[0]) + ay = float(v2[0]) + az = float(v3[0]) + rz = float(v4[0]) + at = math.sqrt(ax*ax + ay*ay + az*az) + + bflags = struct.unpack("H", data[3:5])[0] + psflags = struct.unpack("B", data[5:6])[0] + if len(data) > 27: + pressure = struct.unpack("BBBBBBBBBBBB", data[15:27]) + else: + pressure = [0,0,0,0,0,0,0,0,0,0,0,0,0] + + roll = -math.atan2(ax, math.sqrt(ay*ay + az*az)) + pitch = math.atan2(ay, math.sqrt(ax*ax + az*az)) + + pitch -= math.radians(20); + + xpos = normalize_angle(roll, math.radians(30)) + ypos = normalize_angle(pitch, math.radians(30)) + + # update our sliding window array + sumx.insert(0, xpos) + sumy.insert(0, ypos) + sumx.pop(num_samples) + sumy.pop(num_samples) + + # reset average + xval = 0 + yval = 0 + + # do a sliding window average to remove high frequency + # noise in accelerometer sampling + for i in range(0, num_samples): + xval += sumx[i] + yval += sumy[i] + + axis = struct.unpack("BBBB", data[7:11]) + if xbmc: + for i in range(4): + config = axismap_sixaxis[i] + axis_amount[i] = send_singleaxis(xbmc, axis[i], axis_amount[i], config[0], config[1], config[2]) + + # send the mouse position to xbmc + if mouse_enabled == 1: + xbmc.send_mouse_position(xval/num_samples, yval/num_samples) + + return (bflags, psflags, pressure) + +def send_singleaxis(xbmc, axis, last_amount, mapname, action_min, action_pos): + amount = normalize_axis(axis, 0.30) + if last_amount < 0: + last_action = action_min + elif last_amount > 0: + last_action = action_pos + else: + last_action = None + + if amount < 0: + new_action = action_min + elif amount > 0: + new_action = action_pos + else: + new_action = None + + if last_action and new_action != last_action: + xbmc.send_button_state(map=mapname, button=last_action, amount=0, axis=1) + + if new_action and amount != last_amount: + xbmc.send_button_state(map=mapname, button=new_action, amount=abs(amount), axis=1) + + return amount diff --git a/tools/EventClients/lib/python/xbmcclient.py b/tools/EventClients/lib/python/xbmcclient.py new file mode 100644 index 0000000000..d62f2b9ee3 --- /dev/null +++ b/tools/EventClients/lib/python/xbmcclient.py @@ -0,0 +1,621 @@ +#!/usr/bin/python + +""" +Implementation of XBMC's UDP based input system. + +A set of classes that abstract the various packets that the event server +currently supports. In addition, there's also a class, XBMCClient, that +provides functions that sends the various packets. Use XBMCClient if you +don't need complete control over packet structure. + +The basic workflow involves: + +1. Send a HELO packet +2. Send x number of valid packets +3. Send a BYE packet + +IMPORTANT NOTE ABOUT TIMEOUTS: +A client is considered to be timed out if XBMC doesn't received a packet +at least once every 60 seconds. To "ping" XBMC with an empty packet use +PacketPING or XBMCClient.ping(). See the documentation for details. +""" + +__author__ = "d4rk@xbmc.org" +__version__ = "0.0.3" + +from struct import pack +from socket import * +import time + +MAX_PACKET_SIZE = 1024 +HEADER_SIZE = 32 +MAX_PAYLOAD_SIZE = MAX_PACKET_SIZE - HEADER_SIZE +UNIQUE_IDENTIFICATION = (int)(time.time()) + +PT_HELO = 0x01 +PT_BYE = 0x02 +PT_BUTTON = 0x03 +PT_MOUSE = 0x04 +PT_PING = 0x05 +PT_BROADCAST = 0x06 +PT_NOTIFICATION = 0x07 +PT_BLOB = 0x08 +PT_LOG = 0x09 +PT_ACTION = 0x0A +PT_DEBUG = 0xFF + +ICON_NONE = 0x00 +ICON_JPEG = 0x01 +ICON_PNG = 0x02 +ICON_GIF = 0x03 + +BT_USE_NAME = 0x01 +BT_DOWN = 0x02 +BT_UP = 0x04 +BT_USE_AMOUNT = 0x08 +BT_QUEUE = 0x10 +BT_NO_REPEAT = 0x20 +BT_VKEY = 0x40 +BT_AXIS = 0x80 +BT_AXISSINGLE = 0x100 + +MS_ABSOLUTE = 0x01 + +LOGDEBUG = 0x00 +LOGINFO = 0x01 +LOGNOTICE = 0x02 +LOGWARNING = 0x03 +LOGERROR = 0x04 +LOGSEVERE = 0x05 +LOGFATAL = 0x06 +LOGNONE = 0x07 + +ACTION_EXECBUILTIN = 0x01 +ACTION_BUTTON = 0x02 + +###################################################################### +# Helper Functions +###################################################################### + +def format_string(msg): + """ """ + return msg + "\0" + +def format_uint32(num): + """ """ + return pack ("!I", num) + +def format_uint16(num): + """ """ + if num<0: + num = 0 + elif num>65535: + num = 65535 + return pack ("!H", num) + + +###################################################################### +# Packet Classes +###################################################################### + +class Packet: + """Base class that implements a single event packet. + + - Generic packet structure (maximum 1024 bytes per packet) + - Header is 32 bytes long, so 992 bytes available for payload + - large payloads can be split into multiple packets using H4 and H5 + H5 should contain total no. of packets in such a case + - H6 contains length of P1, which is limited to 992 bytes + - if H5 is 0 or 1, then H4 will be ignored (single packet msg) + - H7 must be set to zeros for now + + ----------------------------- + | -H1 Signature ("XBMC") | - 4 x CHAR 4B + | -H2 Version (eg. 2.0) | - 2 x UNSIGNED CHAR 2B + | -H3 PacketType | - 1 x UNSIGNED SHORT 2B + | -H4 Sequence number | - 1 x UNSIGNED LONG 4B + | -H5 No. of packets in msg | - 1 x UNSIGNED LONG 4B + | -H7 Client's unique token | - 1 x UNSIGNED LONG 4B + | -H8 Reserved | - 10 x UNSIGNED CHAR 10B + |---------------------------| + | -P1 payload | - + ----------------------------- + """ + def __init__(self): + self.sig = "XBMC" + self.minver = 0 + self.majver = 2 + self.seq = 1 + self.maxseq = 1 + self.payloadsize = 0 + self.uid = UNIQUE_IDENTIFICATION + self.reserved = "\0" * 10 + self.payload = "" + return + + + def append_payload(self, blob): + """Append to existing payload + + Arguments: + blob -- binary data to append to the current payload + """ + self.set_payload(self.payload + blob) + + + def set_payload(self, payload): + """Set the payload for this packet + + Arguments: + payload -- binary data that contains the payload + """ + self.payload = payload + self.payloadsize = len(self.payload) + self.maxseq = int((self.payloadsize + (MAX_PAYLOAD_SIZE - 1)) / MAX_PAYLOAD_SIZE) + + + def num_packets(self): + """ Return the number of packets required for payload """ + return self.maxseq + + def get_header(self, packettype=-1, seq=1, maxseq=1, payload_size=0): + """Construct a header and return as string + + Keyword arguments: + packettype -- valid packet types are PT_HELO, PT_BYE, PT_BUTTON, + PT_MOUSE, PT_PING, PT_BORADCAST, PT_NOTIFICATION, + PT_BLOB, PT_DEBUG + seq -- the sequence of this packet for a multi packet message + (default 1) + maxseq -- the total number of packets for a multi packet message + (default 1) + payload_size -- the size of the payload of this packet (default 0) + """ + if packettype < 0: + packettype = self.packettype + header = self.sig + header += chr(self.majver) + header += chr(self.minver) + header += format_uint16(packettype) + header += format_uint32(seq) + header += format_uint32(maxseq) + header += format_uint16(payload_size) + header += format_uint32(self.uid) + header += self.reserved + return header + + def get_payload_size(self, seq): + """Returns the calculated payload size for the particular packet + + Arguments: + seq -- the sequence number + """ + if self.maxseq == 1: + return self.payloadsize + + if seq < self.maxseq: + return MAX_PAYLOAD_SIZE + + return self.payloadsize % MAX_PAYLOAD_SIZE + + + def get_udp_message(self, packetnum=1): + """Construct the UDP message for the specified packetnum and return + as string + + Keyword arguments: + packetnum -- the packet no. for which to construct the message + (default 1) + """ + if packetnum > self.num_packets() or packetnum < 1: + return "" + header = "" + if packetnum==1: + header = self.get_header(self.packettype, packetnum, self.maxseq, + self.get_payload_size(packetnum)) + else: + header = self.get_header(PT_BLOB, packetnum, self.maxseq, + self.get_payload_size(packetnum)) + + payload = self.payload[ (packetnum-1) * MAX_PAYLOAD_SIZE : + (packetnum-1) * MAX_PAYLOAD_SIZE+ + self.get_payload_size(packetnum) ] + return header + payload + + def send(self, sock, addr, uid=UNIQUE_IDENTIFICATION): + """Send the entire message to the specified socket and address. + + Arguments: + sock -- datagram socket object (socket.socket) + addr -- address, port pair (eg: ("127.0.0.1", 9777) ) + uid -- unique identification + """ + self.uid = uid + for a in range ( 0, self.num_packets() ): + try: + sock.sendto(self.get_udp_message(a+1), addr) + return True + except: + return False + + +class PacketHELO (Packet): + """A HELO packet + + A HELO packet establishes a valid connection to XBMC. It is the + first packet that should be sent. + """ + def __init__(self, devicename=None, icon_type=ICON_NONE, icon_file=None): + """ + Keyword arguments: + devicename -- the string that identifies the client + icon_type -- one of ICON_NONE, ICON_JPEG, ICON_PNG, ICON_GIF + icon_file -- location of icon file with respect to current working + directory if icon_type is not ICON_NONE + """ + Packet.__init__(self) + self.packettype = PT_HELO + self.icontype = icon_type + self.set_payload ( format_string(devicename)[0:128] ) + self.append_payload( chr (icon_type) ) + self.append_payload( format_uint16 (0) ) # port no + self.append_payload( format_uint32 (0) ) # reserved1 + self.append_payload( format_uint32 (0) ) # reserved2 + if icon_type != ICON_NONE and icon_file: + self.append_payload( file(icon_file).read() ) + +class PacketNOTIFICATION (Packet): + """A NOTIFICATION packet + + This packet displays a notification window in XBMC. It can contain + a caption, a message and an icon. + """ + def __init__(self, title, message, icon_type=ICON_NONE, icon_file=None): + """ + Keyword arguments: + title -- the notification caption / title + message -- the main text of the notification + icon_type -- one of ICON_NONE, ICON_JPEG, ICON_PNG, ICON_GIF + icon_file -- location of icon file with respect to current working + directory if icon_type is not ICON_NONE + """ + Packet.__init__(self) + self.packettype = PT_NOTIFICATION + self.title = title + self.message = message + self.set_payload ( format_string(title) ) + self.append_payload( format_string(message) ) + self.append_payload( chr (icon_type) ) + self.append_payload( format_uint32 (0) ) # reserved + if icon_type != ICON_NONE and icon_file: + self.append_payload( file(icon_file).read() ) + +class PacketBUTTON (Packet): + """A BUTTON packet + + A button packet send a key press or release event to XBMC + """ + def __init__(self, code=0, repeat=1, down=1, queue=0, + map_name="", button_name="", amount=0, axis=0): + """ + Keyword arguments: + code -- raw button code (default: 0) + repeat -- this key press should repeat until released (default: 1) + Note that queued pressed cannot repeat. + down -- if this is 1, it implies a press event, 0 implies a release + event. (default: 1) + queue -- a queued key press means that the button event is + executed just once after which the next key press is + processed. It can be used for macros. Currently there + is no support for time delays between queued presses. + (default: 0) + map_name -- a combination of map_name and button_name refers to a + mapping in the user's Keymap.xml or Lircmap.xml. + map_name can be one of the following: + "KB" => standard keyboard map ( <keyboard> section ) + "XG" => xbox gamepad map ( <gamepad> section ) + "R1" => xbox remote map ( <remote> section ) + "R2" => xbox universal remote map ( <universalremote> + section ) + "LI:devicename" => LIRC remote map where 'devicename' is the + actual device's name + button_name -- a button name defined in the map specified in map_name. + For example, if map_name is "KB" refering to the + <keyboard> section in Keymap.xml then, valid + button_names include "printscreen", "minus", "x", etc. + amount -- unimplemented for now; in the future it will be used for + specifying magnitude of analog key press events + """ + Packet.__init__(self) + self.flags = 0 + self.packettype = PT_BUTTON + if type (code ) == str: + code = ord(code) + + # assign code only if we don't have a map and button name + if not (map_name and button_name): + self.code = code + else: + self.flags |= BT_USE_NAME + self.code = 0 + if (amount != None): + self.flags |= BT_USE_AMOUNT + self.amount = int(amount) + else: + self.amount = 0 + + if down: + self.flags |= BT_DOWN + else: + self.flags |= BT_UP + if not repeat: + self.flags |= BT_NO_REPEAT + if queue: + self.flags |= BT_QUEUE + if axis == 1: + self.flags |= BT_AXISSINGLE + elif axis == 2: + self.flags |= BT_AXIS + + self.set_payload ( format_uint16(self.code) ) + self.append_payload( format_uint16(self.flags) ) + self.append_payload( format_uint16(self.amount) ) + self.append_payload( format_string (map_name) ) + self.append_payload( format_string (button_name) ) + +class PacketMOUSE (Packet): + """A MOUSE packet + + A MOUSE packets sets the mouse position in XBMC + """ + def __init__(self, x, y): + """ + Arguments: + x -- horitontal position ranging from 0 to 65535 + y -- vertical position ranging from 0 to 65535 + + The range will be mapped to the screen width and height in XBMC + """ + Packet.__init__(self) + self.packettype = PT_MOUSE + self.flags = MS_ABSOLUTE + self.append_payload( chr (self.flags) ) + self.append_payload( format_uint16(x) ) + self.append_payload( format_uint16(y) ) + +class PacketBYE (Packet): + """A BYE packet + + A BYE packet terminates the connection to XBMC. + """ + def __init__(self): + Packet.__init__(self) + self.packettype = PT_BYE + + +class PacketPING (Packet): + """A PING packet + + A PING packet tells XBMC that the client is still alive. All valid + packets act as ping (not just this one). A client needs to ping + XBMC at least once in 60 seconds or it will time out. + """ + def __init__(self): + Packet.__init__(self) + self.packettype = PT_PING + +class PacketLOG (Packet): + """A LOG packet + + A LOG packet tells XBMC to log the message to xbmc.log with the loglevel as specified. + """ + def __init__(self, loglevel=0, logmessage="", autoprint=True): + """ + Keyword arguments: + loglevel -- the loglevel, follows XBMC standard. + logmessage -- the message to log + autoprint -- if the logmessage should automaticly be printed to stdout + """ + Packet.__init__(self) + self.packettype = PT_LOG + self.append_payload( chr (loglevel) ) + self.append_payload( format_string(logmessage) ) + if (autoprint): + print logmessage + +class PacketACTION (Packet): + """An ACTION packet + + An ACTION packet tells XBMC to do the action specified, based on the type it knows were it needs to be sent. + The idea is that this will be as in scripting/skining and keymapping, just triggered from afar. + """ + def __init__(self, actionmessage="", actiontype=ACTION_EXECBUILTIN): + """ + Keyword arguments: + loglevel -- the loglevel, follows XBMC standard. + logmessage -- the message to log + autoprint -- if the logmessage should automaticly be printed to stdout + """ + Packet.__init__(self) + self.packettype = PT_ACTION + self.append_payload( chr (actiontype) ) + self.append_payload( format_string(actionmessage) ) + +###################################################################### +# XBMC Client Class +###################################################################### + +class XBMCClient: + """An XBMC event client""" + + def __init__(self, name ="", icon_file=None, broadcast=False, uid=UNIQUE_IDENTIFICATION, + ip="127.0.0.1"): + """ + Keyword arguments: + name -- Name of the client + icon_file -- location of an icon file, if any (png, jpg or gif) + uid -- unique identification + """ + self.name = str(name) + self.icon_file = icon_file + self.icon_type = self._get_icon_type(icon_file) + self.ip = ip + self.port = 9777 + self.sock = socket(AF_INET,SOCK_DGRAM) + if broadcast: + self.sock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) + self.uid = uid + + + def connect(self, ip=None, port=None): + """Initialize connection to XBMC + ip -- IP Address of XBMC + port -- port that the event server on XBMC is listening on + """ + if ip: + self.ip = ip + if port: + self.port = int(port) + self.addr = (self.ip, self.port) + packet = PacketHELO(self.name, self.icon_type, self.icon_file) + return packet.send(self.sock, self.addr, self.uid) + + + def close(self): + """Close the current connection""" + packet = PacketBYE() + return packet.send(self.sock, self.addr, self.uid) + + + def ping(self): + """Send a PING packet""" + packet = PacketPING() + return packet.send(self.sock, self.addr, self.uid) + + + def send_notification(self, title="", message="", icon_file=None): + """Send a notification to the connected XBMC + Keyword Arguments: + title -- The title/heading for the notifcation + message -- The message to be displayed + icon_file -- location of an icon file, if any (png, jpg, gif) + """ + self.connect() + packet = PacketNOTIFICATION(title, message, + self._get_icon_type(icon_file), + icon_file) + return packet.send(self.sock, self.addr, self.uid) + + + def send_keyboard_button(self, button=None): + """Send a keyboard event to XBMC + Keyword Arguments: + button -- name of the keyboard button to send (same as in Keymap.xml) + """ + if not button: + return + return self.send_button(map="KB", button=button) + + + def send_remote_button(self, button=None): + """Send a remote control event to XBMC + Keyword Arguments: + button -- name of the remote control button to send (same as in Keymap.xml) + """ + if not button: + return + return self.send_button(map="R1", button=button) + + + def release_button(self): + """Release all buttons""" + packet = PacketBUTTON(code=0x01, down=0) + return packet.send(self.sock, self.addr, self.uid) + + + def send_button(self, map="", button="", amount=0): + """Send a button event to XBMC + Keyword arguments: + map -- a combination of map_name and button_name refers to a + mapping in the user's Keymap.xml or Lircmap.xml. + map_name can be one of the following: + "KB" => standard keyboard map ( <keyboard> section ) + "XG" => xbox gamepad map ( <gamepad> section ) + "R1" => xbox remote map ( <remote> section ) + "R2" => xbox universal remote map ( <universalremote> + section ) + "LI:devicename" => LIRC remote map where 'devicename' is the + actual device's name + button -- a button name defined in the map specified in map, above. + For example, if map is "KB" refering to the <keyboard> + section in Keymap.xml then, valid buttons include + "printscreen", "minus", "x", etc. + """ + packet = PacketBUTTON(map_name=str(map), button_name=str(button), amount=amount) + return packet.send(self.sock, self.addr, self.uid) + + def send_button_state(self, map="", button="", amount=0, down=0, axis=0): + """Send a button event to XBMC + Keyword arguments: + map -- a combination of map_name and button_name refers to a + mapping in the user's Keymap.xml or Lircmap.xml. + map_name can be one of the following: + "KB" => standard keyboard map ( <keyboard> section ) + "XG" => xbox gamepad map ( <gamepad> section ) + "R1" => xbox remote map ( <remote> section ) + "R2" => xbox universal remote map ( <universalremote> + section ) + "LI:devicename" => LIRC remote map where 'devicename' is the + actual device's name + button -- a button name defined in the map specified in map, above. + For example, if map is "KB" refering to the <keyboard> + section in Keymap.xml then, valid buttons include + "printscreen", "minus", "x", etc. + """ + if axis: + if amount == 0: + down = 0 + else: + down = 1 + + packet = PacketBUTTON(map_name=str(map), button_name=str(button), amount=amount, down=down, queue=1, axis=axis) + return packet.send(self.sock, self.addr, self.uid) + + def send_mouse_position(self, x=0, y=0): + """Send a mouse event to XBMC + Keywords Arguments: + x -- absolute x position of mouse ranging from 0 to 65535 + which maps to the entire screen width + y -- same a 'x' but relates to the screen height + """ + packet = PacketMOUSE(int(x), int(y)) + return packet.send(self.sock, self.addr, self.uid) + + def send_log(self, loglevel=0, logmessage="", autoprint=True): + """ + Keyword arguments: + loglevel -- the loglevel, follows XBMC standard. + logmessage -- the message to log + autoprint -- if the logmessage should automaticly be printed to stdout + """ + packet = PacketLOG(loglevel, logmessage) + return packet.send(self.sock, self.addr, self.uid) + + def send_action(self, actionmessage="", actiontype=ACTION_EXECBUILTIN): + """ + Keyword arguments: + actionmessage -- the ActionString + actiontype -- The ActionType the ActionString should be sent to. + """ + packet = PacketACTION(actionmessage, actiontype) + return packet.send(self.sock, self.addr, self.uid) + + def _get_icon_type(self, icon_file): + if icon_file: + if icon_file.lower()[-3:] == "png": + return ICON_PNG + elif icon_file.lower()[-3:] == "gif": + return ICON_GIF + elif icon_file.lower()[-3:] == "jpg": + return ICON_JPG + return ICON_NONE diff --git a/tools/EventClients/lib/python/zeroconf.py b/tools/EventClients/lib/python/zeroconf.py new file mode 100644 index 0000000000..9fc82f5909 --- /dev/null +++ b/tools/EventClients/lib/python/zeroconf.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python +""" +Simple wrapper around Avahi +""" + +__author__ = "d4rk@xbmc.org" +__version__ = "0.1" + +try: + import time + import dbus, gobject, avahi + from dbus import DBusException + from dbus.mainloop.glib import DBusGMainLoop +except Exception, e: + print "Zeroconf support disabled. To enable, install the following Python modules:" + print " dbus, gobject, avahi" + pass + +SERVICE_FOUND = 1 +SERVICE_LOST = 2 + +class Browser: + """ Simple Zeroconf Browser """ + + def __init__( self, service_types = {} ): + """ + service_types - dictionary of services => handlers + """ + self._stop = False + self.loop = DBusGMainLoop() + self.bus = dbus.SystemBus( mainloop=self.loop ) + self.server = dbus.Interface( self.bus.get_object( avahi.DBUS_NAME, '/' ), + 'org.freedesktop.Avahi.Server') + self.handlers = {} + + for type in service_types.keys(): + self.add_service( type, service_types[ type ] ) + + + def add_service( self, type, handler = None ): + """ + Add a service that the browser should watch for + """ + self.sbrowser = dbus.Interface( + self.bus.get_object( + avahi.DBUS_NAME, + self.server.ServiceBrowserNew( + avahi.IF_UNSPEC, + avahi.PROTO_UNSPEC, + type, + 'local', + dbus.UInt32(0) + ) + ), + avahi.DBUS_INTERFACE_SERVICE_BROWSER) + self.handlers[ type ] = handler + self.sbrowser.connect_to_signal("ItemNew", self._new_item_handler) + self.sbrowser.connect_to_signal("ItemRemove", self._remove_item_handler) + + + def run(self): + """ + Run the gobject event loop + """ + # Don't use loop.run() because Python's GIL will block all threads + loop = gobject.MainLoop() + context = loop.get_context() + while not self._stop: + if context.pending(): + context.iteration( True ) + else: + time.sleep(1) + + def stop(self): + """ + Stop the gobject event loop + """ + self._stop = True + + + def _new_item_handler(self, interface, protocol, name, stype, domain, flags): + if flags & avahi.LOOKUP_RESULT_LOCAL: + # local service, skip + pass + + self.server.ResolveService( + interface, + protocol, + name, + stype, + domain, + avahi.PROTO_UNSPEC, + dbus.UInt32(0), + reply_handler = self._service_resolved_handler, + error_handler = self._error_handler + ) + return + + + def _remove_item_handler(self, interface, protocol, name, stype, domain, flags): + if self.handlers[ stype ]: + # FIXME: more details needed here + try: + self.handlers[ stype ]( SERVICE_LOST, { 'type' : stype, 'name' : name } ) + except: + pass + + + def _service_resolved_handler( self, *args ): + service = {} + service['type'] = str( args[3] ) + service['name'] = str( args[2] ) + service['address'] = str( args[7] ) + service['hostname'] = str( args[5] ) + service['port'] = int( args[8] ) + + # if the service type has a handler call it + try: + if self.handlers[ args[3] ]: + self.handlers[ args[3] ]( SERVICE_FOUND, service ) + except: + pass + + + def _error_handler( self, *args ): + print 'ERROR: %s ' % str( args[0] ) + + +if __name__ == "__main__": + def service_handler( found, service ): + print "---------------------" + print ['Found Service', 'Lost Service'][found-1] + for key in service.keys(): + print key+" : "+str( service[key] ) + + browser = Browser( { + '_xbmc-events._udp' : service_handler, + '_xbmc-web._tcp' : service_handler + } ) + browser.run() + |