EventGhost/EventGhost

View on GitHub
plugins/XBCDRC/__init__.py

Summary

Maintainability
F
5 days
Test Coverage
# Xbox(original) remote control plugin for EventGhost by jinxdone. Based on the Generic HID plugin by Bartman.
#
"""<rst>
Xbox remote control plugin, based on the Generic Human Interface Device (HID) plugin.

For this plugin to work you need the `XBCDRC drivers <http://www.redcl0ud.com/xbcd_rc.html>`__ by redcl0ud

|

.. image:: xbcdrc.png
   :align: center
"""

eg.RegisterPlugin(
    name = "Xbox Remote Control",
    author = (
        "jinxdone",
        "Bartman",
    ),
    version = "0.1.3.348",
    kind = "remote",
    guid = "{00F14717-738A-40C0-8BEC-F56F5D9AAF7E}",
    canMultiLoad = False,
    description = __doc__,
    icon = (
        "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAeJJREFUOMul"
        "k09I02EYxz/Pu/e33J80R6UUlEsolDCCYkSJ5S3oD1RCHaJDSEhQEHjpItShSxAU1M2SCCLo0EU6"
        "RV2KQCwkaqXk2PyTy0bbbGtuv/ftEK0xo0Y+3+vz+fLl+fKIxbKcUSxzFIAoccWRYq2QaCmKltLv"
        "BEZkWz/T0q5i0iLxv8H+Dkls6mWGVZID0AAWowJDkt91wc4m4mRkXF7aYRuphr298rQtTM5ZQNl5"
        "Uw8glUcUJe7RmyQDnaSfDOJO+b1v7eVCj5x3HresL23ojhB8FWVy9IztKjPVLSiPuKcHSTUdI/Nh"
        "DD31HDe7EfaG0fEQI4/C9kjlvq6OaVzrWXND0gdLBPd34fMdRi8s4j5oYvhZqC5aU42fR2zD6maM"
        "D3TaUJAcKnOL7QXJD9Rk0HZN3rQ2EwiV0BNRTOorxc27WbnikIwuqbT6BuvOytjJPhqddwTv3cZ4"
        "/Jh6B919kex0EvfhXWKLd+y+MmAr1HCA8asxZo5fIQXKIhiLBcH41vL93AsSPUMk2KNf/2LKcHAn"
        "c5feMxmO8A2UtSyV8lI6MUC27z6zTiufygaBDuY7rzPha6T4J7BSIHbHKb5s7efjz3RB8moLSaVx"
        "/wVXStdRknbmRDy4xrWe//3GH4uN7Ni6Xm5TAAAAAElFTkSuQmCC"

    )
)

import time
import binascii
import ctypes
import _winreg
import sys
import threading
import win32con
import win32event
import win32file
import wx.lib.mixins.listctrl as listmix

from ctypes import Structure, Union, c_byte, c_char, c_int, c_long, c_ulong, c_ushort, c_wchar
from ctypes import pointer, byref, sizeof, POINTER
from ctypes.wintypes import ULONG, BOOLEAN

class Text:
    manufacturer = "Manufacturer"
    deviceName = "Device Name"
    connected = "Connected"
    eventName = "Event prefix (optional):"
    yes = "Yes"
    no = "No"
    enduringEvents = "Trigger enduring events for buttons"
    multipleDeviceOptions = "Options for multiple same devices"
    noOtherPort = "Use selected device only if connected to current port"
    useFirstDevice = "Use first device found"
    errorFind = "Error finding an Xbox IR receiver"
    errorOpen = "Error opening an Xbox IR receiver"
    errorRead = "Error reading an Xbox IR receiver"
    errorRetrieval = "Error getting HID device info."
    errorReportLength = "Report length must not be zero for device "
    errorMultipleDevices = "Multiple devices found. Don't know which to use."
    errorInvalidDataIndex = "Found data index not defined as button or control value."
    vendorID = "Vendor ID "
    unknownCode = "Unknown code received: "
    configurationText = "If you receive unknown codes please report these to the EventGhost forum so we can add support for them"

#structures for ctypes
class GUID(Structure):
    _fields_ = [
        ("Data1", c_ulong),
        ("Data2", c_ushort),
        ("Data3", c_ushort),
        ("Data4", c_byte * 8)
    ]

class SP_DEVICE_INTERFACE_DATA(Structure):
    _fields_ = [("cbSize", c_ulong),
        ("InterfaceClassGuid", GUID),
        ("Flags", c_ulong),
        ("Reserved", POINTER(ULONG))
    ]

class SP_DEVICE_INTERFACE_DETAIL_DATA_A(Structure):
    _fields_ = [("cbSize", c_ulong),
        ("DevicePath", c_char * 255)
    ]

class HIDD_ATTRIBUTES(Structure):
    _fields_ = [("cbSize", c_ulong),
        ("VendorID", c_ushort),
        ("ProductID", c_ushort),
        ("VersionNumber", c_ushort)
    ]

class HIDP_CAPS(Structure):
    _fields_ = [
        ("Usage", c_ushort),
        ("UsagePage", c_ushort),
        ("InputReportByteLength", c_ushort),
        ("OutputReportByteLength", c_ushort),
        ("FeatureReportByteLength", c_ushort),
        ("Reserved", c_ushort * 17),
        ("NumberLinkCollectionNodes", c_ushort),
        ("NumberInputButtonCaps", c_ushort),
        ("NumberInputValueCaps", c_ushort),
        ("NumberInputDataIndices", c_ushort),
        ("NumberOutputButtonCaps", c_ushort),
        ("NumberOutputValueCaps", c_ushort),
        ("NumberOutputDataIndices", c_ushort),
        ("NumberFeatureButtonCaps", c_ushort),
        ("NumberFeatureValueCaps", c_ushort),
        ("NumberFeatureDataIndices", c_ushort)
    ]

class HIDP_CAPS_UNION(Union):
    class HIDP_BUTTON_CAPS_RANGE(Structure):
        _fields_ = [
            ("UsageMin", c_ushort),
            ("UsageMax", c_ushort),
            ("StringMin", c_ushort),
            ("StringMax", c_ushort),
            ("DesignatorMin", c_ushort),
            ("DesignatorMax", c_ushort),
            ("DataIndexMin", c_ushort),
            ("DataIndexMax", c_ushort)
        ]

    class HIDP_BUTTON_CAPS_NOT_RANGE(Structure):
        _fields_ = [
            ("Usage", c_ushort),
            ("Reserved1", c_ushort),
            ("StringIndex", c_ushort),
            ("Reserved2", c_ushort),
            ("DesignatorIndex", c_ushort),
            ("Reserved3", c_ushort),
            ("DataIndex", c_ushort),
            ("Reserved4", c_ushort)
        ]

    _fields_ = [
        ("Range", HIDP_BUTTON_CAPS_RANGE),
        ("NotRange", HIDP_BUTTON_CAPS_NOT_RANGE)
    ]

class HIDP_BUTTON_CAPS(Structure):
    _fields_ = [
        ("UsagePage", c_ushort),
        ("ReportID", c_char),
        ("IsAlias", BOOLEAN),
        ("BitField", c_ushort),
        ("LinkCollection", c_ushort),
        ("LinkUsage", c_ushort),
        ("LinkUsagePage", c_ushort),
        ("IsRange", BOOLEAN),
        ("IsStringRange", BOOLEAN),
        ("IsDesignatorRange", BOOLEAN),
        ("IsAbsolute", BOOLEAN),
        ("Reserved", c_ulong * 10),
        ("Info", HIDP_CAPS_UNION)
    ]

class HIDP_VALUE_CAPS(Structure):
    _fields_ = [
        ("UsagePage", c_ushort),
        ("ReportID", c_char),
        ("IsAlias", BOOLEAN),
        ("BitField", c_ushort),
        ("LinkCollection", c_ushort),
        ("LinkUsage", c_ushort),
        ("LinkUsagePage", c_ushort),
        ("IsRange", BOOLEAN),
        ("IsStringRange", BOOLEAN),
        ("IsDesignatorRange", BOOLEAN),
        ("IsAbsolute", BOOLEAN),
        ("HasNull", BOOLEAN),
        ("Reserved", c_char),
        ("BitSize", c_ushort),
        ("ReportCount", c_ushort),
        ("Reserved2", c_ushort * 5),
        ("UnitsExp", c_ulong),
        ("Units", c_ulong),
        ("LogicalMin", c_long),
        ("LogicalMax", c_long),
        ("PhysicalMin", c_long),
        ("PhysicalMax", c_long),
        ("Info", HIDP_CAPS_UNION)
    ]

class HIDP_DATA(Structure):
    class HIDP_DATA_VALUE(Union):
        _fields_ = [
            ("RawValue", c_ulong),
            ("On", BOOLEAN),
        ]

    _fields_ = [
        ("DataIndex", c_ushort),
        ("Reserved", c_ushort),
        ("Data", HIDP_DATA_VALUE)
    ]

# Flags controlling what is included in the device information set built
# by SetupDiGetClassDevs
DIGCF_DEFAULT         = 0x00000001  # only valid with DIGCF_DEVICEINTERFACE
DIGCF_PRESENT         = 0x00000002
DIGCF_ALLCLASSES      = 0x00000004
DIGCF_PROFILE         = 0x00000008
DIGCF_DEVICEINTERFACE = 0x00000010

#constants to identify the device info
DEVICE_PATH = 0
VENDOR_ID = 1
VENDOR_STRING = 2
PRODUCT_ID = 3
PRODUCT_STRING = 4
VERSION_NUMBER = MAX_INDEX = 5


#helper class to iterate, find and open hid devices
class HIDHelper:
    text = Text
    deviceList = []

    def __init__(self):
        self.UpdateDeviceList()

    def UpdateDeviceList(self):
        self.deviceList = []

        #dll references
        setupapiDLL = ctypes.windll.setupapi
        hidDLL =  ctypes.windll.hid

        #prepare Interfacedata
        interfaceInfo = SP_DEVICE_INTERFACE_DATA()
        interfaceInfo.cbSize = sizeof(interfaceInfo)

        #prepare InterfaceDetailData Structure
        interfaceDetailData = SP_DEVICE_INTERFACE_DETAIL_DATA_A()
        interfaceDetailData.cbSize = 5

        #prepare HIDD_ATTRIBUTES
        hiddAttributes = HIDD_ATTRIBUTES()
        hiddAttributes.cbSize = sizeof(hiddAttributes)

        #get guid for HID device class
        g = GUID()
        hidDLL.HidD_GetHidGuid(byref(g))

        #get handle to the device information set
        hinfo = setupapiDLL.SetupDiGetClassDevsA(byref(g), None, None,
            DIGCF_PRESENT + DIGCF_DEVICEINTERFACE)

        #enumerate devices
        i = 0
        while setupapiDLL.SetupDiEnumDeviceInterfaces(hinfo,
            None, byref(g), i, byref(interfaceInfo)):
            device = {}
            i += 1

            #get the required size
            requiredSize = c_ulong()
            setupapiDLL.SetupDiGetDeviceInterfaceDetailA(hinfo,
                byref(interfaceInfo), None, 0, byref(requiredSize), None)
            if requiredSize.value > 250:
                eg.PrintError(self.text.errorRetrieval)
                continue #prevent a buffer overflow

            #get the actual info
            setupapiDLL.SetupDiGetDeviceInterfaceDetailA(
                hinfo,
                byref(interfaceInfo),
                byref(interfaceDetailData),
                requiredSize,
                pointer(requiredSize),
                None
            )
            device[DEVICE_PATH] = interfaceDetailData.DevicePath

            #get handle to HID device
            try:
                hidHandle = win32file.CreateFile(
                    device[DEVICE_PATH],
                    win32con.GENERIC_READ | win32con.GENERIC_WRITE,
                    win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
                    None,
                    win32con.OPEN_EXISTING,
                    0,
                    0
                )
                #skipping devices which cannot be opened
                #(e.g. mice & keyboards, which are opened exclusivly by OS)
                if int(hidHandle) <= 0:
                    continue
            except:
                continue

            #getting additional info
            hidDLL.HidD_GetAttributes(int(hidHandle), byref(hiddAttributes))
            device[VENDOR_ID] = hiddAttributes.VendorID
            device[PRODUCT_ID] = hiddAttributes.ProductID
            device[VERSION_NUMBER] = hiddAttributes.VersionNumber

            #prepare string buffer for device info strings
            hidpStringType = c_wchar * 128
            infoStr = hidpStringType()

            #getting manufacturer
            result = hidDLL.HidD_GetManufacturerString(
                int(hidHandle), byref(infoStr), ctypes.sizeof(infoStr))
            if not result or len(infoStr.value) == 0:
                #build a generic ManufacturerString with the vendor ID
                device[VENDOR_STRING] = self.text.vendorID + str(hiddAttributes.VendorID)
            else:
                device[VENDOR_STRING] = infoStr.value

            #getting device name
            result = hidDLL.HidD_GetProductString(
                int(hidHandle), byref(infoStr), ctypes.sizeof(infoStr))
            if not result or len(infoStr.value) == 0:
                #getting product name via registry
                devicePathSplit = device[DEVICE_PATH][4:].split("#")
                regHandle = _winreg.OpenKey(
                    _winreg.HKEY_LOCAL_MACHINE,
                    "SYSTEM\\CurrentControlSet\\Enum\\" + devicePathSplit[0] + \
                    "\\" + devicePathSplit[1] + "\\" + devicePathSplit[2])
                device[PRODUCT_STRING], regType = _winreg.QueryValueEx(regHandle, "DeviceDesc")
                _winreg.CloseKey(regHandle)
            else:
                device[PRODUCT_STRING] = infoStr.value

            #close handle
            win32file.CloseHandle(hidHandle)

            #add device to internal list
            self.deviceList.append(device)

        #end loop
        #destroy deviceinfolist
        setupapiDLL.SetupDiDestroyDeviceInfoList(hinfo)


    #gets the devicePath
    #the devicePath parameter is only used with multiple same devices
    def GetDevicePath(self,
        noOtherPort,
        devicePath,
        vendorID,
        productID,
        versionNumber,
        useFirstDevice = False
    ):
        found = 0
        path = ""
        for item in self.deviceList:
            if noOtherPort:
                #just search for devicepath
                if item[DEVICE_PATH] == devicePath:
                    #found right device
                    return devicePath
            else:
                #find the right vendor and product ids
                if item[VENDOR_ID] == vendorID \
                    and item[PRODUCT_ID] == productID:
                    found = found + 1
                    if (item[DEVICE_PATH] == devicePath) or (useFirstDevice):
                        #found right device
                        return item[DEVICE_PATH]
                    path = item[DEVICE_PATH]

        if found == 1:
            return path

        #multiple devices found
        #don't know which to use
        if found > 1:
            eg.PrintError(self.text.errorMultipleDevices)

        return None


class HIDThread(threading.Thread):
    def __init__(self,
        plugin,
        helper,
        enduringEvents,
        noOtherPort,
        devicePath,
        vendorID,
        vendorString,
        productID,
        productString,
        versionNumber,
        useFirstDevice
    ):
        self.plugin = plugin
        self.text = Text
        self.deviceName = vendorString + " " + productString
        self.abort = False
        self._overlappedRead = win32file.OVERLAPPED()
        self._overlappedRead.hEvent = win32event.CreateEvent(None, 1, 0, None)

        self.xbcdrc_mapping = {
            '006d50a': 'DISPLAY', '0066a05': 'DISPLAY', '0066afd': 'DISPLAY',
            '006e20a': 'REVERSE', '00671f5': 'REVERSE', '006710d': 'REVERSE',
            '006ea0a': 'PLAY', '00675f5': 'PLAY', '006750d': 'PLAY',
            '006e30a': 'FORWARD', '0067105': 'FORWARD', '00671fd': 'FORWARD',
            '006dd0a': 'SKIP-', '0066e05': 'SKIP-', '0066efd': 'SKIP-',
            '006e00a': 'STOP', '00670f5': 'STOP', '006700d': 'STOP',
            '006e60a': 'PAUSE', '00673f5': 'PAUSE', '006730d': 'PAUSE',
            '006df0a': 'SKIP+', '0066f05': 'SKIP+', '0066ffd': 'SKIP+',
            '006e50a': 'TITLE', '0067205': 'TITLE', '00672fd': 'TITLE',
            '006c30a': 'INFO', '0066105': 'INFO', '00661fd': 'INFO',
            '006a60a': 'UP', '00653f5': 'UP', '006530d': 'UP',
            '006a70a': 'DOWN', '0065305': 'DOWN', '00653fd': 'DOWN',
            '006a90a': 'LEFT', '0065405': 'LEFT', '00654fd': 'LEFT',
            '006a80a': 'RIGHT', '00654f5': 'RIGHT', '006540d': 'RIGHT',
            '0060b0a': 'SELECT', '0060505': 'SELECT', '00605fd': 'SELECT',
            '006f70a': 'MENU', '0067b05': 'MENU', '0067bfd': 'MENU',
            '006d80a': 'BACK', '0066cf5': 'BACK', '0066c0d': 'BACK',
            '006ce0a': '1', '00667f5': '1', '006670d': '1',
            '006cd0a': '2', '0066605': '2', '00666fd': '2',
            '006cc0a': '3', '00666f5': '3', '006660d': '3',
            '006cb0a': '4', '0066505': '4', '00665fd': '4',
            '006ca0a': '5', '00665f5': '5', '006650d': '5',
            '006c90a': '6', '0066405': '6', '00664fd': '6',
            '006c80a': '7', '00664f5': '7', '006640d': '7',
            '006c70a': '8', '0066305': '8', '00663fd': '8',
            '006c60a': '9', '00663f5': '9', '006630d': '9',
            '006cf0a': '0', '0066705': '0', '00667fd': '0',
            '0064f0a': 'AUDIO',
            '006af0a': 'ENT.',
            '006d100': 'LT',
            '006890a': 'LAST',
            '006c000': 'MUTE',
            '006e70a': 'A*B',
            '006f90a': 'EXIT',
            '0067f0a': 'D',
            '0068c0a': 'REC'
        }

        #getting devicePath
        self.devicePath = helper.GetDevicePath(
            noOtherPort,
            devicePath,
            vendorID,
            productID,
            versionNumber,
            useFirstDevice
        )
        plugin.devicePath = self.devicePath

        if not self.devicePath:
            plugin.PrintError(self.text.errorFind)
            self.plugin.status = 2
            return

        threading.Thread.__init__(self, name = self.devicePath)

        #setting members
        self.helper = helper
        self.enduringEvents = enduringEvents
        self.start()


    def AbortThread(self):
        self.abort = True
        win32event.SetEvent(self._overlappedRead.hEvent)
        self.plugin.status = 2

    def run(self):
        #open file/devcice
        try:
            handle = win32file.CreateFile(
                self.devicePath,
                    win32con.GENERIC_READ | win32con.GENERIC_WRITE,
                    win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
                None, # no security
                win32con.OPEN_EXISTING,
                win32con.FILE_ATTRIBUTE_NORMAL | win32con.FILE_FLAG_OVERLAPPED,
                0
            )
        except:
            self.plugin.PrintError(self.text.errorOpen)
            self.plugin.status = 3
            return

        #getting data to get the right buffer size
        hidDLL =  ctypes.windll.hid
        setupapiDLL = ctypes.windll.setupapi

        #get preparsed data
        preparsedData = c_ulong()
        result = hidDLL.HidD_GetPreparsedData(
            int(handle),
            ctypes.byref(preparsedData)
        )

        #getCaps
        hidpCaps = HIDP_CAPS()
        result = hidDLL.HidP_GetCaps(preparsedData, ctypes.byref(hidpCaps))

        n = hidpCaps.InputReportByteLength
        if n == 0:
            self.abort = True
            self.plugin.PrintError(self.text.errorReportLength + self.deviceName)

        rt = c_int(0)   #report type input
        rl = c_ulong(n)  #report length
        maxDataL = hidDLL.HidP_MaxDataListLength(rt, preparsedData)

        #getting button caps
        bCapsArrL = c_ushort(hidpCaps.NumberInputButtonCaps)
        bCapsArrType = HIDP_BUTTON_CAPS * bCapsArrL.value
        bCapsArr = bCapsArrType()

        hidDLL.HidP_GetButtonCaps(
            rt,
            ctypes.byref(bCapsArr),
            ctypes.byref(bCapsArrL),
            preparsedData
        )

        #getting value caps
        vCapsArrL = c_ushort(hidpCaps.NumberInputValueCaps)
        vCapsArrType = HIDP_VALUE_CAPS * vCapsArrL.value
        vCapsArr = vCapsArrType()

        hidDLL.HidP_GetValueCaps(
            rt,
            ctypes.byref(vCapsArr),
            ctypes.byref(vCapsArrL),
            preparsedData
        )

        #parsing caps
        # prepare a list to find and store for each index
        # whether it is a button or value
        oldValues = {}
        dataIndexType = [0] * hidpCaps.NumberInputDataIndices

        #list entries depending on caps
        for i in range(bCapsArrL.value):
            if bCapsArr[i].IsRange:
                for ii in range(
                    bCapsArr[i].Info.Range.DataIndexMin,
                    bCapsArr[i].Info.Range.DataIndexMax + 1
                ):
                    dataIndexType[ii] = 1
            else:
                ii = bCapsArr[i].Info.NotRange.DataIndex
                dataIndexType[ii] = 1


        for i in range(vCapsArrL.value):
            if vCapsArr[i].IsRange:
                for ii in range(
                    vCapsArr[i].Info.Range.DataIndexMin,
                    vCapsArr[i].Info.Range.DataIndexMax + 1
                ):
                    dataIndexType[ii] = 2
                    oldValues[ii] = sys.maxint
            else:
                ii = vCapsArr[i].Info.NotRange.DataIndex
                dataIndexType[ii] = 2
                oldValues[ii] = sys.maxint

        #prepare data array with maximum possible length
        DataArrayType = HIDP_DATA * maxDataL
        data = DataArrayType()

        #initiziling finished. setting status
        self.plugin.status = 1

        # Some added variables -jinxdone
        btnup_count = 0
        old_event = ""
        event_gen = False

        while not self.abort:
            #try to read and wait for an event to happen
            try:
                win32event.ResetEvent(self._overlappedRead.hEvent)
                rc, buf = win32file.ReadFile(handle, n, self._overlappedRead)
                #waiting for an event
                win32event.WaitForSingleObject(
                    self._overlappedRead.hEvent,
                    win32event.INFINITE
                )
            except:
                self.plugin.PrintError(self.text.errorRead)
                self.abort = True
                #device got disconnected so set status to waiting
                self.plugin.status = 2


            #parse data
            if len(buf) == n and not self.abort:
                mystr = binascii.hexlify(str(buf))[3:10]
                if mystr[0] == "0":
                    if mystr == "0000000":
                        continue
                    try:
                        mystr = self.xbcdrc_mapping[mystr]
                    except:
                        self.plugin.PrintError(self.plugin.text.unknownCode + mystr)
                        continue
                    #if mystr == old_event:
                    #    continue
                    #old_event = mystr
                    #button down event
                    if self.enduringEvents:
                        btnup_count = 0
                        if event_gen == False:
                            #enduring events
                            self.plugin.TriggerEnduringEvent(mystr)
                            event_gen = True
                    else:
                        #normal events
                        self.plugin.TriggerEvent(mystr)
                else:
                    #button up event
                    if self.enduringEvents and event_gen == True:
                        btnup_count += 1
                        if btnup_count > 13:
                            #print("Ending event..")
                            self.plugin.EndLastEvent()
                            btnup_count = 0
                            event_gen = False

        #loop aborted
        if self.enduringEvents:
            self.plugin.EndLastEvent()

        win32file.CloseHandle(handle)

        #free references
        hidDLL.HidD_FreePreparsedData(ctypes.byref(preparsedData))

        #HID thread finished



class XBCDRC(eg.PluginClass):
    helper = None
    text = Text
    thread = None
    status = -1
    # -1: not initizilized
    #  0: stopped
    #  1: running
    #  2: waiting for device
    #  3: error

    # Added to detect removing of the device, so state is updated and
    # can be recognized again when reattached -jinxdone
    def DeviceRemoved(self, event):
        """method to reconnect a disconnect device"""
        if self.status == 1:
            tmppath = event.payload[0].lower()
            if tmppath == self.devicePath:
                self.status = 2


    def ReconnectDevice(self, event):
        """method to reconnect a disconnect device"""
        if self.status == 2:
            #updating devicelist
            self.helper.UpdateDeviceList()

            #check if the right device was connected
            #getting devicePath
            self.devicePath = self.helper.GetDevicePath(
                self.noOtherPort,
                self.devicePath,
                self.vendorID,
                self.productID,
                self.versionNumber,
                self.useFirstDevice
            )
            if not self.devicePath:
                #wrong device
                return

            #create thread
            self.thread = HIDThread(
                self,
                self.helper,
                self.enduringEvents,
                self.noOtherPort,
                self.devicePath,
                self.vendorID,
                self.vendorString,
                self.productID,
                self.productString,
                self.versionNumber,
                self.useFirstDevice
            )

    # Modified version because of the changed config dialog.. -jinxdone
    def GetLabel(self, eventName, enduringEvents):
        return "XBCDRC"


    def __start__(self,
        eventName,
        enduringEvents
    ):

            #saving parameters so they cn be used to reconnect a device
        self.eventName = eventName
        self.enduringEvents = enduringEvents

        #saving parameters so they cn be used to reconnect a device
        self.eventName = eventName
        self.enduringEvents = enduringEvents
        #Settings from generic hid plugin set with xbox ir defaults..
        self.noOtherPort = False
        self.devicePath = ''
        self.vendorID = 1118
        self.vendorString = 'Vendor ID 1118'
        self.productID = 644
        self.productString = ''
        self.versionNumber = 0
        self.useFirstDevice = True

        if eventName:
            self.info.eventPrefix = eventName
        else:
            self.info.eventPrefix = "XBCDRC"
        #ensure helper object is up to date
        if not self.helper:
            self.helper = HIDHelper()
        else:
            self.helper.UpdateDeviceList()

        #create thread
        self.thread = HIDThread(
            self,
            self.helper,
            enduringEvents,
            self.noOtherPort,
            self.devicePath,
            self.vendorID,
            self.vendorString,
            self.productID,
            self.productString,
            self.versionNumber,
            self.useFirstDevice
        )
        #Bind plugin to RegisterDeviceNotification message
        eg.Bind("System.DeviceAttached", self.ReconnectDevice)

        # Added event -jinxdone
        #Bind plugin to RegisterDeviceNotification message
        eg.Bind("System.DeviceRemoved", self.DeviceRemoved)

    def __stop__(self):
        self.thread.AbortThread()

        #unbind from RegisterDeviceNotification message
        eg.Unbind("System.DeviceAttached", self.ReconnectDevice)
        self.status = 0



    def Configure(self,
        eventName = "",
        enduringEvents = True
    ):
        #ensure helper object is up to date
        if not self.helper:
            self.helper = HIDHelper()
        else:
            self.helper.UpdateDeviceList()

        panel = eg.ConfigPanel(self, resizable=True)

        #layout

        #sizers
        optionsSizer = wx.GridBagSizer(0, 5)

        #eventname
        optionsSizer.Add(
            wx.StaticText(panel, -1, self.text.eventName),
            (0, 0),
            flag = wx.ALIGN_CENTER_VERTICAL)
        eventNameCtrl = wx.TextCtrl(panel, value = eventName)
        eventNameCtrl.SetMaxLength(32)
        optionsSizer.Add(eventNameCtrl, (0, 1), (1, 2), flag = wx.EXPAND)

        #checkbox for enduring event option
        enduringEventsCtrl = wx.CheckBox(panel, -1, self.text.enduringEvents)
        enduringEventsCtrl.SetValue(enduringEvents)
        optionsSizer.Add(enduringEventsCtrl, (1, 0), (1, 3))

        panel.sizer.Add(optionsSizer, 0, wx.TOP, 10)

        configurationText = wx.StaticText(panel, -1, self.text.configurationText, (0, 0), (350, 40))
        panel.sizer.Add(configurationText, 0, wx.TOP, 30)

        while panel.Affirmed():
            panel.SetResult(
                eventNameCtrl.GetValue(),
                enduringEventsCtrl.GetValue()
            )