Notice: This forum has been recovered from an old backup, so some content, links, and dates may be outdated. The forum is currently read-only while we restore sign-in and registration functionality. Details
If you find this forum valuable and would like to help keep it online, donations to help cover hosting and domain costs are greatly appreciated, but never expected. You can support the forum through Buy Me a Coffee or Ko-fi. Thank you for helping preserve the EventGhost community.
If you find this forum valuable and would like to help keep it online, donations to help cover hosting and domain costs are greatly appreciated, but never expected. You can support the forum through Buy Me a Coffee or Ko-fi. Thank you for helping preserve the EventGhost community.
HID PlugIn
- Bitmonster
- Site Admin
- Posts: 2239
- Joined: Mon Feb 06, 2006 10:28 pm
- Bitmonster
- Site Admin
- Posts: 2239
- Joined: Mon Feb 06, 2006 10:28 pm
Hier jetzt nochmal ohne die print Ausgaben und ├╝berlangen Zeilen.
Irgendwie scheinen andere Programme aber noch bessere Namen für das Gerät herauszukriegen.
Jetzt w├ñre mal interessant, ob andere Leute mit diversen Fernbedienungen damit was anfangen k├Ânnen. Ich hab nur die Zusatztasten auf der Logitech-Tastatur und ein altes GameVoice.
Jetzt fehlt nur noch das Event-Mapping-Feature
Edit info.py und eine Bemerkung ergänzt
Irgendwie scheinen andere Programme aber noch bessere Namen für das Gerät herauszukriegen.
Jetzt w├ñre mal interessant, ob andere Leute mit diversen Fernbedienungen damit was anfangen k├Ânnen. Ich hab nur die Zusatztasten auf der Logitech-Tastatur und ein altes GameVoice.
Jetzt fehlt nur noch das Event-Mapping-Feature
Edit info.py und eine Bemerkung ergänzt
Code: Select all
name = "HID"
author = "Bartman"
version = "1.0.0"
description = "Basic communication with HID devices."Code: Select all
import binascii
import ctypes
import eg
import _winreg
import sys
import threading
import win32con
import win32event
import win32file
import wx
import wx.lib.mixins.listctrl as listmix
from ctypes import Structure, c_ulong, c_ushort, c_byte, c_char
from ctypes import pointer, byref, sizeof, POINTER
from ctypes.wintypes import ULONG
class Text:
deviceName = "Device Name"
vendorID = "Vendor ID"
productID = "Product ID"
connected = "Connected"
eventName = "Event Name (optional)"
yes = "Yes"
no = "No"
noteEventName = "Event names will be saved only for selected devices."
noteReselect = "You may have to reselect the device, if it is plugged in another USB port."
errorOpen = "Error opening HID device:"
errorRead = "Error reading HID device:"
#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 PREPARSED_DATA(Structure):
_fields_ = [("Addr", POINTER(c_ulong)) ]
# 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
class HIDListCtrl(wx.ListCtrl,
listmix.ListCtrlAutoWidthMixin,
listmix.CheckListCtrlMixin,
listmix.TextEditMixin):
text = Text
deviceList = {} #create a dict to track devices and line in ListCtrl
def __init__(self, parent, selected):
wx.ListCtrl.__init__(self, parent, -1, pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.LC_REPORT | wx.BORDER_NONE)
devices = {}
#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)):
i += 1
#get the required size
requiredSize = c_ulong()
setupapiDLL.SetupDiGetDeviceInterfaceDetailA(hinfo,
byref(interfaceInfo), None, 0, byref(requiredSize), None)
if requiredSize.value > 250:
continue #prevent a buffer overflow
#get the actual info
setupapiDLL.SetupDiGetDeviceInterfaceDetailA(
hinfo,
byref(interfaceInfo),
byref(interfaceDetailData),
requiredSize,
pointer(requiredSize),
None
)
#get friendly name of the device via registry
devicePath = interfaceDetailData.DevicePath
devicePathSplit = devicePath[4:].split("#")
regHandle = _winreg.OpenKey(
_winreg.HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Enum\\" + devicePathSplit[0] + \
"\\" + devicePathSplit[1] + "\\" + devicePathSplit[2])
friendlyName, type = _winreg.QueryValueEx(regHandle, "DeviceDesc")
_winreg.CloseKey(regHandle)
#get handle to HID device
try:
hidHandle = win32file.CreateFile(
devicePath,
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))
#close handle
win32file.CloseHandle(hidHandle)
devices[devicePath] = {
"name":friendlyName,
"eventName":"",
"devicePath":devicePath,
"vendorID":hiddAttributes.VendorID,
"productID":hiddAttributes.ProductID,
"versionNumber":hiddAttributes.VersionNumber
}
#destroy deviceinfolist
setupapiDLL.SetupDiDestroyDeviceInfoList(hinfo)
#make a new dict with both lists
devEnum = {}
for key, item in devices.items():
devEnum[key] = item
#add currently not connected devices and update event names
for key, item in selected.items():
devEnum[key] = item
#create GUI
self.InsertColumn(0, self.text.deviceName)
self.InsertColumn(1, self.text.vendorID)
self.InsertColumn(2, self.text.productID)
self.InsertColumn(3, self.text.connected)
self.InsertColumn(4, self.text.eventName)
listmix.TextEditMixin.__init__(self)
listmix.CheckListCtrlMixin.__init__(self)
self.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnBeginEdit)
#populating list
for key, item in devEnum.items():
index = self.InsertStringItem(sys.maxint, item["name"])
if selected.has_key(key): #selected
self.CheckItem(index)
self.SetStringItem(index, 1, str(item["vendorID"]))
self.SetStringItem(index, 2, str(item["productID"]))
if devices.has_key(key): #currently connected?
self.SetStringItem(index, 3, self.text.yes)
else:
self.SetStringItem(index, 3, self.text.no)
self.SetStringItem(index, 4, item["eventName"])
self.SetItemData(index, index)
self.deviceList[index] = item
#layout
self.SetColumnWidth(0, wx.LIST_AUTOSIZE_USEHEADER)
size = self.GetColumnWidth(0)
self.SetColumnWidth(0, wx.LIST_AUTOSIZE)
self.SetColumnWidth(0, max(size, self.GetColumnWidth(0)))
self.SetColumnWidth(1, wx.LIST_AUTOSIZE_USEHEADER)
self.SetColumnWidth(2, wx.LIST_AUTOSIZE_USEHEADER)
self.SetColumnWidth(3, wx.LIST_AUTOSIZE_USEHEADER)
self.SetColumnWidth(3, self.GetColumnWidth(3))
self.SetColumnWidth(4, wx.LIST_AUTOSIZE_USEHEADER)
size = self.GetColumnWidth(4)
self.SetColumnWidth(4, wx.LIST_AUTOSIZE)
self.SetColumnWidth(4, max(size, self.GetColumnWidth(4)))
width = 0;
for i in range(self.GetColumnCount()):
width += self.GetColumnWidth(i)
self.SetMinSize((width, -1))
listmix.ListCtrlAutoWidthMixin.__init__(self)
#returns the selected devices
def GetSelected(self):
selectedDevices = {}
item = self.GetTopItem()
while item != -1:
index = self.GetItemData(item)
if self.IsChecked(index):
self.deviceList[index]["eventName"] = \
self.GetItem(index, 4).GetText()
selectedDevices[self.deviceList[index]["devicePath"]] = \
self.deviceList[index]
item = self.GetNextItem(item)
return selectedDevices
#catch event to prevent all but the last col from being edited
def OnBeginEdit(self, event):
if event.m_col == 4:
event.Allow()
else:
event.Veto()
class HIDThread(threading.Thread):
def __init__(self, deviceName, devicePath, eventName):
self.text = Text
threading.Thread.__init__(self, name = devicePath)
self.deviceName = deviceName
self.devicePath = devicePath
if len(eventName) != 0:
self.eventName = eventName + "."
else:
self.eventName = ""
self.abort = False
self._overlappedRead = win32file.OVERLAPPED()
self._overlappedRead.hEvent = win32event.CreateEvent(None, 1, 0, None)
self.start()
def AbortThread(self):
self.abort = True
win32event.SetEvent(self._overlappedRead.hEvent)
def run(self):
#open file
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:
eg.PrintError(self.text.errorOpen + self.deviceName)
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))
#free references
hidDLL.HidD_FreePreparsedData(ctypes.byref(preparsedData))
n = hidpCaps.InputReportByteLength
while not self.abort:
#print "HID: Looping"
try:
win32event.ResetEvent(self._overlappedRead.hEvent)
buf = win32file.AllocateReadBuffer(n)
rc, buf = win32file.ReadFile(handle, buf, self._overlappedRead)
#print "HID: Waiting"
win32event.WaitForSingleObject(
self._overlappedRead.hEvent,
win32event.INFINITE
)
read = str(buf)
if len(read):
eg.TriggerEvent(
self.eventName + binascii.hexlify(read).upper(),
prefix = "HID"
)
except:
eg.PrintError(self.text.errorRead + self.deviceName)
self.abort = True
win32file.CloseHandle(handle)
#print "HID thread finished"
class HID(eg.RawReceiverPlugin):
canMultiLoad = False
text = Text
threads = {}
def __start__(self, devices = {}):
#print "HID start"
for (key, value) in devices.items():
self.threads[key] = HIDThread(
value["name"],
value["devicePath"],
value["eventName"]
)
def __stop__(self):
#print "HID stop"
for (key, value) in self.threads.items():
#print "HID: stopping thread for", key
self.threads[key].AbortThread()
del(self.threads[key])
def Configure(self, devices = {}):
dialog = eg.ConfigurationDialog(self, resizeable=True)
#building dialog
hidList = HIDListCtrl(dialog, devices)
dialog.sizer.Add(hidList, 1, flag = wx.EXPAND)
dialog.sizer.Add(
wx.StaticText(dialog, -1, self.text.noteEventName),
flag = wx.ALIGN_CENTER_VERTICAL
)
dialog.sizer.Add(
wx.StaticText(dialog, -1, self.text.noteReselect),
flag = wx.ALIGN_CENTER_VERTICAL
)
if dialog.AffirmedShowModal():
return (hidList.GetSelected(), )
- Bitmonster
- Site Admin
- Posts: 2239
- Joined: Mon Feb 06, 2006 10:28 pm
Tja, wenn man da noch etwas findet, dann wahrscheinlich irgendwo in der Registry. Der Devicemanager ist bei HID-Devices aber auch nicht sehr ausführlich. Aber vielleicht findet man irgendwo noch etwas zu den Device und Vendor-IDs.Bartman wrote:Irgendwie scheinen andere Programme aber noch bessere Namen für das Gerät herauszukriegen.
Ich werde das Plugin mal in die n├ñchste Beta mit aufnehmen, damit zumindest der User mit der Firefly mini mal sehen kann, ob da etwas kommt.Bartman wrote:Jetzt w├ñre mal interessant, ob andere Leute mit diversen Fernbedienungen damit was anfangen k├Ânnen. Ich hab nur die Zusatztasten auf der Logitech-Tastatur und ein altes GameVoice.
Jupp, ich habe hier zur Zeit aber nur ein Gamepad, dass mit dem Plugin funktioniert. Und da sind die Ereignisse nicht so einfach zu mappen, weil es f├╝r jeden Button ein Bit gibt. Zudem kommt auch noch f├╝r ein Poti eine Zahl. Da muss man den Eventstring schon richtig mit einem Skript zerlegen.Bartman wrote:Jetzt fehlt nur noch das Event-Mapping-Feature
- Bitmonster
- Site Admin
- Posts: 2239
- Joined: Mon Feb 06, 2006 10:28 pm
Gezielt an ein bestimmtes Fenster?Bartman wrote:Wie kann man eigentlich Mausrad hoch/runter an ein Fenster schicken?
Also allgemein geht das mit mouse_event bzw. SendInput:
mouse_event
SendInput
Will man das speziell an ein hwnd schicken, dann m├╝sste das wohl irgendwie mit SendMessage und WM_VSCROLL gehen. Auf die Schnelle habe ich das hier gefunden: click
- Bitmonster
- Site Admin
- Posts: 2239
- Joined: Mon Feb 06, 2006 10:28 pm
Ich bin gerade am überlegen, wie man zukünftig solche FBs für den User am einfachsten konfigurierbar machen kann. Da gibt es ja noch unterschiedliche Typen, wie z.B. welche die recht viele Events senden wie Joysticks und wie wir bei der Firefly mini gesehen haben auch welche die ButtonUp Events senden. Die Konfiguration bräuchte daher wohl noch ein paar mehr Optionen. Z.B. Timeout je nach Device. Vielleicht wäre es deshalb ratsam schon jetzt den Ansatz aufzugeben, dass der User nur ein Plugin für alle HID-Devices hat, sondern für jedes Device eine weitere Instanz des Plugins hinzufügt.
Hatte ich auch ├╝berlegt.
Insbesondere wenn die Option zum Schreiben auf das Gerät noch umsetzen will.
Au├ƒerdem k├Ânnte man die Identifikation nach VendorID und ProductID umstellen. Dann m├╝sste man das Ger├ñt nach dem Umstecken nicht neu ausw├ñhlen. Gibt dann nur Probleme, wenn man zwei gleiche Ger├ñte hat.
Insbesondere wenn die Option zum Schreiben auf das Gerät noch umsetzen will.
Au├ƒerdem k├Ânnte man die Identifikation nach VendorID und ProductID umstellen. Dann m├╝sste man das Ger├ñt nach dem Umstecken nicht neu ausw├ñhlen. Gibt dann nur Probleme, wenn man zwei gleiche Ger├ñte hat.
- Bitmonster
- Site Admin
- Posts: 2239
- Joined: Mon Feb 06, 2006 10:28 pm
Als einzige GUID kommt derzeit die der Geräteklasse zum Einsatz. Zur Zeit nutze ich den DevicePath zur Identifikation des Gerätes, über den man den Zugriff bekommt. Der ändert sich beim Umstecken definitiv.
Ich wüsste gar nicht ob das Gerät selbst eine GUID bekommt.
Alternativ k├Ânnte man auch alle Ger├ñte nach der gesuchten VendorID und ProductID absuchen und nur wenn man mehrere gleiche Ger├ñte hat auf den DevicePath zur├╝ckfallen.
kann man eigentlich auch sch├Ânere Namen als plugin #x bei mehrfach geladenen PlugIns erzeugen?
beim Directory Watcher Plugin kommt noch ein Fehler beim Starten.
Ich wüsste gar nicht ob das Gerät selbst eine GUID bekommt.
Alternativ k├Ânnte man auch alle Ger├ñte nach der gesuchten VendorID und ProductID absuchen und nur wenn man mehrere gleiche Ger├ñte hat auf den DevicePath zur├╝ckfallen.
kann man eigentlich auch sch├Ânere Namen als plugin #x bei mehrfach geladenen PlugIns erzeugen?
beim Directory Watcher Plugin kommt noch ein Fehler beim Starten.
Code: Select all
Error starting plugin: Directory Watcher
Traceback (most recent call last) (881):
File "D:\Media\EventGhost\eg\TreeItems\PluginItem.py", line 105, in StartPlugin
TypeError: __start__() takes exactly 3 arguments (1 given)- Bitmonster
- Site Admin
- Posts: 2239
- Joined: Mon Feb 06, 2006 10:28 pm
- Bitmonster
- Site Admin
- Posts: 2239
- Joined: Mon Feb 06, 2006 10:28 pm
Ich k├Ânnte (und werde auch) GetLabel() f├╝r Plugins implementieren, aber mal sollte aufpassen, dass die Namen nicht zu lang werden. Denn bei den Actions wird der Plugin Name ja vorangestellt. Sonst kommt da ein ellenlanger String bei raus. Bei reinen Event-Plugins ist es nat├╝rlich nicht ganz so schlimm.
- Bitmonster
- Site Admin
- Posts: 2239
- Joined: Mon Feb 06, 2006 10:28 pm
Ich habe gerade mal die 884 hochgeladen. Diese hat nun auch GetLabel() f├╝r Plugins. Man sollte aber selber aufpassen, dass man den Namen des Plugins dann bei allen ActionClass.GetLabel() auch richtig voranstellt, indem man das Feld self.plugin.label auswertet.
Bei dieser Version habe ich die Men├╝s etwas anders angeordnet. Genauer gesagt habe ich ein neues Men├╝ "Konfiguration" hinzugef├╝gt, f├╝r die Befehle, die sich auf dem Baum beziehen.
Und dann gibt es noch eine Neuerung beim Plugin-Schreiben:
Die __info__.py ist nicht mehr notwendig. Die Informationen k├Ânnen jetzt auch direkt in die __init__.py geschrieben werden. Wie das l├ñuft habe ich vorl├ñufig mal hier beschrieben:
http://www.eventghost.org/wiki/Writing_ ... PluginInfo
Man kann auch gleich ein Icon mit in den Source bauen, indem man noch das Feld "icon" zu der PluginInfo Klasse hinzuf├╝gt. Um den notwendigen String aus einem PNG-File zu erstellen, habe ich mal dieses Makro gebastelt:
Einfach starten, das PNG auswählen und der String steht dann in der Zwischenablage.
Als Beispiel habe ich mal das d-box2 Plugin so umgebaut.
Bei dieser Version habe ich die Men├╝s etwas anders angeordnet. Genauer gesagt habe ich ein neues Men├╝ "Konfiguration" hinzugef├╝gt, f├╝r die Befehle, die sich auf dem Baum beziehen.
Und dann gibt es noch eine Neuerung beim Plugin-Schreiben:
Die __info__.py ist nicht mehr notwendig. Die Informationen k├Ânnen jetzt auch direkt in die __init__.py geschrieben werden. Wie das l├ñuft habe ich vorl├ñufig mal hier beschrieben:
http://www.eventghost.org/wiki/Writing_ ... PluginInfo
Man kann auch gleich ein Icon mit in den Source bauen, indem man noch das Feld "icon" zu der PluginInfo Klasse hinzuf├╝gt. Um den notwendigen String aus einem PNG-File zu erstellen, habe ich mal dieses Makro gebastelt:
Code: Select all
<?xml version="1.0" encoding="UTF-8" ?>
<EventGhost Version="884">
<Macro Name="Convert PNG to Python" Expanded="True">
<Action Name="Convert PNG to Python">
EventGhost.PythonScript(u'import base64\nimport wx\n\ndef Main():\n dialog = wx.FileDialog(None, wildcard="PNG files (*.png)|*.png")\n dialog.ShowModal()\n filePath = dialog.GetPath()\n dialog.Destroy()\n if not filePath:\n return\n \n fd = open(filePath, "rb")\n data = base64.b64encode(fd.read())\n fd.close()\n\n s = " icon = (\\n"\n while len(data) > 68:\n s += \' "\' + data[:68] + \'"\\n\'\n data = data[68:]\n s += \' "\' + data + \'"\\n\'\n s += " )\\n"\n \n eg.result = s\n\n\neg.CallWait(Main)\n\n')
</Action>
<Action>
System.SetClipboard(u'{eg.result}')
</Action>
</Macro>
</EventGhost>Als Beispiel habe ich mal das d-box2 Plugin so umgebaut.
- Bitmonster
- Site Admin
- Posts: 2239
- Joined: Mon Feb 06, 2006 10:28 pm
Ich habe mal ein paar Versuche mit der RawInput API gemacht und die waren leider recht ern├╝chternd. Die Nachrichten ├╝ber RawInput kommen leider nach dem Keyboard-Hook. Somit kann ich dem Keyboard-Hook nicht explizit sagen, welche Tastendr├╝cke er blocken soll. Einzige M├Âglichkeit w├ñre es, kurzerhand alle "echten" Tastendr├╝cke im Keyboard-Hook zu blocken, dann zu warten bis sie bei dem RawInput-Code ankommen und dort alle nicht zu blockenden Tastendr├╝cke wieder ├╝ber SendInput zu generieren. Bei der Sache w├╝rde ich mich aber nicht sonderlich wohl f├╝hlen. Aber ich werde es trotzdem irgendwann mal versuchen.