1. 程式人生 > >PyQt4實現USB裝置插入到彈出的檢測(Windows)

PyQt4實現USB裝置插入到彈出的檢測(Windows)

直沒怎麼看過 QextSerialPort 中windows下列舉串列埠的具體實現(原因?對win32api不瞭解,看不懂啊_)

經過一段時間的學習,對QextSerialPort總算理清了一些,於是,就可以用python實現列舉串列埠了,也可以檢測串列埠的載入與移除了。

不過呢?除了"usb轉的串列埠"容易被移除外,想移除串列埠還真不太容易。所以可能沒什麼實際作用。於是呢?

寫到最後,還是改主意了。用它來實現USB裝置的插入與移除吧.

先記錄一下程式編寫中遇到的困難

class DEV_BROADCAST_DEVICEINTERFACE(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("dbcc_size",  wintypes.DWORD), 
          ("dbcc_devicetype",  wintypes.DWORD), 
          ("dbcc_reserved",  wintypes.DWORD), 
          ("dbcc_classguid",  GUID), 
          ("dbcc_name",  ctypes.c_wchar*260)]

  • 一開始就在它上面遇到了問題,因為按照win32的標頭檔案給結構體,dbcc_name 是長度為1的陣列。

我也將其定義為長度1的陣列 ctypes.c_wchar*1

  • 然後 RegisterDeviceNotification 呼叫不成功,通過FormatError得到“引數錯誤”

    解決方案,新增 _pack_ = 1後錯誤消失

  • 然而到最後,僅僅這樣還不行,因為無法通過 dbcc_name 得到需要的字串(因為它長度是1) 所以最終採用的方案如上
對 PyQt4 不熟,所以想當然認為其餘Qt4提供的簽名應該一樣了:
bool QWidget::winEvent ( MSG * message, long * result )

結果執行時老是警告:引數個數不對。

原來在PyQt4裡,真正的函式簽名應該是:

def winEvent(self, msg):     return False,id(msg)

注:PySide 尚未提供該方法,所以本程式無法在PySide下執行:

原始碼:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
Copyright (C) 2010 dbzhang800
All rights reserved.
'''

import sys
import ctypes
import ctypes.wintypes as wintypes 
from PyQt4.QtCore import *
from PyQt4.QtGui import *

NULL = 0
INVALID_HANDLE_VALUE = -1
DBT_DEVTYP_DEVICEINTERFACE = 5
DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000
DBT_DEVICEREMOVECOMPLETE = 0x8004
DBT_DEVICEARRIVAL = 0x8000
WM_DEVICECHANGE = 0x0219

user32 = ctypes.windll.user32
RegisterDeviceNotification = user32.RegisterDeviceNotificationW
UnregisterDeviceNotification = user32.UnregisterDeviceNotification

class GUID(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("Data1", ctypes.c_ulong),
                ("Data2", ctypes.c_ushort),
                ("Data3", ctypes.c_ushort),
                ("Data4", ctypes.c_ubyte * 8)]
                
class DEV_BROADCAST_DEVICEINTERFACE(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("dbcc_size",  wintypes.DWORD), 
                ("dbcc_devicetype",  wintypes.DWORD), 
                ("dbcc_reserved",  wintypes.DWORD), 
                ("dbcc_classguid",  GUID), 
                ("dbcc_name",  ctypes.c_wchar*260)]

class DEV_BROADCAST_HDR(ctypes.Structure):
    _fields_ = [("dbch_size", wintypes.DWORD),
                ("dbch_devicetype", wintypes.DWORD),
                ("dbch_reserved", wintypes.DWORD)]


GUID_DEVCLASS_PORTS = GUID(0x4D36E978, 0xE325, 0x11CE,
        (ctypes.c_ubyte*8)(0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18))
GUID_DEVINTERFACE_USB_DEVICE = GUID(0xA5DCBF10L, 0x6530,0x11D2, 
        (ctypes.c_ubyte*8)(0x90, 0x1F, 0x00,0xC0, 0x4F, 0xB9, 0x51, 0xED))


class Window(QWidget):
    def __init__(self,  parent=None):
        super(Window,  self).__init__(parent)
        self.resize(QSize(600,  320))
        self.setWindowTitle("Device Notify")
        self.setupNotification()
        vbox = QVBoxLayout(self)
        vbox.addWidget(QLabel("Log window:",  self))
        self.logEdit = QPlainTextEdit(self)
        vbox.addWidget(self.logEdit)
        self.setLayout(vbox)
        
    def setupNotification(self):
        dbh = DEV_BROADCAST_DEVICEINTERFACE()
        dbh.dbcc_size = ctypes.sizeof(DEV_BROADCAST_DEVICEINTERFACE)
        dbh.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE
        dbh.dbcc_classguid = GUID_DEVINTERFACE_USB_DEVICE #GUID_DEVCLASS_PORTS
        self.hNofity = RegisterDeviceNotification(int(self.winId()), 
                                                ctypes.byref(dbh), 
                                                DEVICE_NOTIFY_WINDOW_HANDLE)
        if self.hNofity == NULL:
            print ctypes.FormatError(), int(self.winId())
            print "RegisterDeviceNotification failed"
            
    def closeEvent(self,  evt):
        if self.hNofity:
            UnregisterDeviceNotification(self.hNofity)
        super(Window,  self).closeEvent(evt)

    def winEvent(self, message):
        if message.message == WM_DEVICECHANGE:
            self.onDeviceChanged(message.wParam, message.lParam)
            return True, id(message)
        return False, id(message)

    def onDeviceChanged(self, wParam, lParam):
        if DBT_DEVICEARRIVAL == wParam:
            self.logEdit.appendHtml("<font color=blue>Device Arrival:</font>")
        elif DBT_DEVICEREMOVECOMPLETE == wParam:
            self.logEdit.appendHtml("<font color=red>Device Removed:</font>")

        if (DBT_DEVICEARRIVAL == wParam or DBT_DEVICEREMOVECOMPLETE == wParam):
            dbh = DEV_BROADCAST_HDR.from_address(lParam)
            if dbh.dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE:
                dbd = DEV_BROADCAST_DEVICEINTERFACE.from_address(lParam)
                self.logEdit.appendPlainText(dbd.dbcc_name)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = Window()
    w.show()
    sys.exit(app.exec_())