rene-aguirre / pywinusb

USB / HID windows helper library
BSD 3-Clause "New" or "Revised" License
205 stars 62 forks source link

PNP for tkinter #75

Open IFXGAWU opened 2 years ago

IFXGAWU commented 2 years ago

Hello, I tried the pnp example for wx and it works fine for me. Is there maybe an example available for tkinter available? tkinter does not provide a handle, do you have some hints for me how to implement pnp for tkinter? Thanks in advance, GW

rene-aguirre commented 2 years ago

It seems there is a way to get the HWND, this looks like a good starting point: https://web.archive.org/web/20200731093951id_/http://effbot.org/tkinterbook/wm.htm#Tkinter.Wm.frame-method

IFXGAWU commented 2 years ago

Thanks for your very fast response! Could you please give me an additional helping hand?

class MyFrame(ttk.Frame,hid.HidPnPWindowMixin):

a device so we could easily discriminate wich devices to look at

my_hid_target = hid.HidDeviceFilter(vendor_id = target_vendor_id, product_id = target_product_id)

def __init__(self, parent):
    ttk.WmFrame.__init__(self,parent) #tkinter Frame has only 2 parameters
    # wx.Frame.__init__(self,parent,-1,"Re-plug your USB HID device, watch the command window!...")
    #             Frame(parent, id=ID_ANY, title="", pos=DefaultPosition, size=DefaultSize, style=DEFAULT_FRAME_STYLE, name=FrameNameStr)
    hid.HidPnPWindowMixin.__init__(self, self.GetHandle())
    ttk.EVT_CLOSE(self, self.on_close)

Result: AttributeError: module 'tkinter.ttk' has no attribute 'WmFrame'

Do you have the time to check what's going wrong? Or do I have to adapt the "HidPnPWindowMixin" for tkinter? Thanks & BR

rene-aguirre commented 2 years ago

WMFrame seems to be legacy.

I check in a REPL session that a tkinter.Frame exposes a winfo_id() method, google shows this will be the frame HWND in Windows

IFXGAWU commented 2 years ago

Hello Rene, It looks like that's how it works.

`# """ Plug and Play example

This script requires wxPython, but it could be easily (really!) changed to work with any GUI working on windows, just make you you pass your frame window handler to the HidPnPWindowMixin.init initialization function.

A hook will be inserted on the message handler so the window could be used as a target for PnP events, for now this are HID class wise, this means you'll have to test if your device 'plug' status has changed. """ from tkinter import * from tkinter import ttk import tkinter as tk from tkinter import messagebox from tkinter import filedialog import tkinter as tk

import pywinusb.hid as hid import sys from time import sleep

feel free to test

target_vendor_id = 0x640 target_product_id = 0xA10

class MyFrame(ttk.Frame,hid.HidPnPWindowMixin):

a device so we could easily discriminate wich devices to look at

my_hid_target = hid.HidDeviceFilter(vendor_id = target_vendor_id, product_id = target_product_id)

def __init__(self, parent):
    ttk.Frame.__init__(self,parent)
    # wx.Frame.__init__(self,parent,-1,"Re-plug your USB HID device, watch the command window!...")
    #             Frame(parent, id=ID_ANY, title="", pos=DefaultPosition, size=DefaultSize, style=DEFAULT_FRAME_STYLE, name=FrameNameStr)
    #hid.HidPnPWindowMixin.__init__(self, self.GetHandle())
    hid.HidPnPWindowMixin.__init__(self, self.winfo_id()) #or try winfo_id()
    #ttk.EVT_CLOSE(self, self.on_close)

    self.device = None #no hid device... yet

    # kick the pnp engine
    self.on_hid_pnp()

def on_hid_pnp(self, hid_event = None):
    """This function will be called on per class event changes, so we need
    to test if our device has being connected or is just gone"""
    # keep old reference for UI updates
    old_device = self.device

    if hid_event:
        print("Hey, a hid device just %s!" % hid_event)

    if hid_event == "connected":
        # test if our device is available
        if self.device:
            # see, at this point we could detect multiple devices!
            # but... we only want just one

            pass
        else:
            self.test_for_connection()
    elif hid_event == "disconnected":
        # the hid object is automatically closed on disconnection we just
        # test if still is plugged (important as the object might be
        # closing)
        if self.device and not self.device.is_plugged():
            self.device = None
            print("you removed my hid device!")
    else:
        # poll for devices
        self.test_for_connection()

    if old_device != self.device:
        # update ui
        pass

def test_for_connection(self):
    all_items =  MyFrame.my_hid_target.get_devices()
    if all_items:
        # at this point, what we decided to be a valid hid target is
        # already plugged
        if len(all_items) == 1:
            # this is easy, we only have a single hid device
            self.device = all_items[0]
        else:
            # at this point you might have multiple scenarios
            grouped_items = MyFrame.my_hid_target.get_devices_by_parent()
            print("%d devices now connected" % len(grouped_items))
            if len(grouped_items) > 1:
                # 1) Really you have multiple devices connected so, make
                # your rules, how do you help your user to handle multiple
                # devices?
                # maybe you here will find out wich is the new device, and
                # tag this device so is easily identified (i.e. the WiiMote
                # uses LEDs), or just your GUI shows some arbitrary
                # identification for the user (device 2 connected)
                pass
            else:
                # 2) We have a single physical device, but the descriptors
                # might might cause the OS to report is as multiple devices
                # (collections maybe) so, what would be your target device?
                # if you designed the device firmware, you already know the
                # answer...  otherwise one approach might be to browse the
                # hid usages for a particular target...  anyway, this could
                # be complex, especially handling multiple physical devices
                # that are reported as multiple hid paths (objects)...
                # so...  I recommend you creating a proxy class that is
                # able to handle all your 'per parent id' grouped devices,
                # (like a single .open() able to handle your buch of
                # HidDevice() items

                pass
            # but... we just arbitrarly select the first hid object path
            # (how creative!)
            self.device = all_items[0]
    if self.device:
        self.device.open()

        print("got my device: %s!" % repr(self.device))
        self.device.out_report = self.device.find_output_reports()[0]
        #sleep(2000)
        buffer[1] = 0x02   # Testdata
        buffer[2] = 0x00   # Testdata
        buffer[3] = 0x01   # Testdata
        buffer[4] = 0      # Testdata
        buffer[5] = 0      # Testdata
        self.device.out_report.set_raw_data(buffer)
        self.device.out_report.send()
    else:
        print("saddly my device is not here... yet :-( ")

def on_close(self, event):
    event.Skip()
    if self.device:
        self.device.close()

if name == "main": buffer = [0x00] * 65 buffer[0] = 0x00

root = Tk()

#frame = Frame(root)
#frame.pack()

#frame = MyFrame(None)
frame = MyFrame(root)
frame.pack()

root.title("HID test pywinusb with TKINTER 2022/05/08")
root.geometry("1800x1000")
root.geometry("1920x1000+-8+0")

#frame.Show()   

root.mainloop()`