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.

HID PlugIn

Allgemeines zum Thema EventGhost
User avatar
Bitmonster
Site Admin
Posts: 2239
Joined: Mon Feb 06, 2006 10:28 pm

Post by Bitmonster »

("Reserved", c_ushort * 17),
statt
("Reserved", c_byte * 17),
User avatar
Bitmonster
Site Admin
Posts: 2239
Joined: Mon Feb 06, 2006 10:28 pm

Post by Bitmonster »

Jo, bei mir scheint es nicht mehr zu knallen. Das Plugin sieht schon ganz schick aus. Da hast du echt gutes ctypes-Coding hingelegt.
Bartman
Plugin Developer
Posts: 881
Joined: Sun Feb 12, 2006 9:03 am

Post by Bartman »

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

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(), )
        
User avatar
Bitmonster
Site Admin
Posts: 2239
Joined: Mon Feb 06, 2006 10:28 pm

Post by Bitmonster »

Bartman wrote:Irgendwie scheinen andere Programme aber noch bessere Namen für das Gerät herauszukriegen.
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: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.
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 fehlt nur noch das Event-Mapping-Feature
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
Plugin Developer
Posts: 881
Joined: Sun Feb 12, 2006 9:03 am

Post by Bartman »

Das mit dem Bitset scheint normal zu sein.
Anders geht das mit der festen Reportlänge auch kaum.
Solange man nur eine Taste dr├╝ckt ist das ja auch nicht das Problem.
Wie kann man eigentlich Mausrad hoch/runter an ein Fenster schicken?
User avatar
Bitmonster
Site Admin
Posts: 2239
Joined: Mon Feb 06, 2006 10:28 pm

Post by Bitmonster »

Bartman wrote:Wie kann man eigentlich Mausrad hoch/runter an ein Fenster schicken?
Gezielt an ein bestimmtes Fenster?
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
User avatar
Bitmonster
Site Admin
Posts: 2239
Joined: Mon Feb 06, 2006 10:28 pm

Post by Bitmonster »

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.
Bartman
Plugin Developer
Posts: 881
Joined: Sun Feb 12, 2006 9:03 am

Post by Bartman »

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.
User avatar
Bitmonster
Site Admin
Posts: 2239
Joined: Mon Feb 06, 2006 10:28 pm

Post by Bitmonster »

Ändert sich beim Umstecken auch die GUID des Devices?
Bartman
Plugin Developer
Posts: 881
Joined: Sun Feb 12, 2006 9:03 am

Post by Bartman »

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.

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)
User avatar
Bitmonster
Site Admin
Posts: 2239
Joined: Mon Feb 06, 2006 10:28 pm

Post by Bitmonster »

Tja, nur wie stellst du dir einen besseren Namen vor?

Der andere Bug sollte in 883 gefixt sein.
Bartman
Plugin Developer
Posts: 881
Joined: Sun Feb 12, 2006 9:03 am

Post by Bartman »

Bitmonster wrote:Tja, nur wie stellst du dir einen besseren Namen vor?

Der andere Bug sollte in 883 gefixt sein.
Z.B. Zoom Player at host:port
HID GamePad XY
Also dass das Plugin den Namen dynmaisch vergeben kann. Ähnlich den Actions.
User avatar
Bitmonster
Site Admin
Posts: 2239
Joined: Mon Feb 06, 2006 10:28 pm

Post by Bitmonster »

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.
User avatar
Bitmonster
Site Admin
Posts: 2239
Joined: Mon Feb 06, 2006 10:28 pm

Post by Bitmonster »

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:

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>
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.
User avatar
Bitmonster
Site Admin
Posts: 2239
Joined: Mon Feb 06, 2006 10:28 pm

Post by Bitmonster »

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.
Post Reply