0% found this document useful (0 votes)
64 views7 pages

Import Import Import From Import From Import Import: Drive STR STR STR

This Python code listens for changes to connected drives in Windows and triggers callbacks when drives are plugged in or removed. It uses the pywin32 library to create a window and listen for WM_DEVICECHANGE messages from the operating system. When a drive change is detected, it gets a current list of drives using Windows Management Instrumentation (WMI) and passes this list to the registered callback function. The code provides an example callback that backs up any drive labeled "ABDUS".

Uploaded by

Paulo
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
64 views7 pages

Import Import Import From Import From Import Import: Drive STR STR STR

This Python code listens for changes to connected drives in Windows and triggers callbacks when drives are plugged in or removed. It uses the pywin32 library to create a window and listen for WM_DEVICECHANGE messages from the operating system. When a drive change is detected, it gets a current list of drives using Windows Management Instrumentation (WMI) and passes this list to the registered callback function. The code provides an example callback that backs up any drive labeled "ABDUS".

Uploaded by

Paulo
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 7

import json

import logging
import subprocess
from dataclasses import dataclass
from typing import Callable, List
import win32api, win32con, win32gui
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@dataclass
class Drive:
letter: str
label: str
drive_type: str
@property
def is_removable(self) -> bool:
return self.drive_type == 'Removable Disk'

class DeviceListener:
"""
Listens to Win32 `WM_DEVICECHANGE` messages
and trigger a callback when a device has been plugged in or out
See: https://docs.microsoft.com/en-us/windows/win32/devio/wm-
devicechange
"""
WM_DEVICECHANGE_EVENTS = {
0x0019: ('DBT_CONFIGCHANGECANCELED', 'A request to
change the current configuration (dock or undock) has been canceled.'),
0x0018: ('DBT_CONFIGCHANGED', 'The current configuration
has changed, due to a dock or undock.'),
0x8006: ('DBT_CUSTOMEVENT', 'A custom event has
occurred.'),
0x8000: ('DBT_DEVICEARRIVAL', 'A device or piece of media
has been inserted and is now available.'),
0x8001: ('DBT_DEVICEQUERYREMOVE', 'Permission is
requested to remove a device or piece of media. Any application can
deny this request and cancel the removal.'),
0x8002: ('DBT_DEVICEQUERYREMOVEFAILED', 'A request
to remove a device or piece of media has been canceled.'),
0x8004: ('DBT_DEVICEREMOVECOMPLETE', 'A device or
piece of media has been removed.'),
0x8003: ('DBT_DEVICEREMOVEPENDING', 'A device or
piece of media is about to be removed. Cannot be denied.'),
0x8005: ('DBT_DEVICETYPESPECIFIC', 'A device-specific
event has occurred.'),
0x0007: ('DBT_DEVNODES_CHANGED', 'A device has been
added to or removed from the system.'),
0x0017: ('DBT_QUERYCHANGECONFIG', 'Permission is
requested to change the current configuration (dock or undock).'),
0xFFFF: ('DBT_USERDEFINED', 'The meaning of this message
is user-defined.'),
}
def __init__(self, on_change: Callable[[List[Drive]], None]):
self.on_change = on_change
def _create_window(self):
"""
Create a window for listening to messages
https://docs.microsoft.com/en-
us/windows/win32/learnwin32/creating-a-window#creating-the-window
See also: https://docs.microsoft.com/en-
us/windows/win32/api/winuser/nf-winuser-createwindoww
:return: window hwnd
"""
wc = win32gui.WNDCLASS()
wc.lpfnWndProc = self._on_message
wc.lpszClassName = self.__class__.__name__
wc.hInstance = win32api.GetModuleHandle(None)
class_atom = win32gui.RegisterClass(wc)
return win32gui.CreateWindow(class_atom,
self.__class__.__name__, 0, 0, 0, 0, 0, 0, 0, wc.hInstance, None)
def start(self):
logger.info(f'Listening to drive changes')
hwnd = self._create_window()
logger.debug(f'Created listener window with hwnd={hwnd:x}')
logger.debug(f'Listening to messages')
win32gui.PumpMessages()
def _on_message(self, hwnd: int, msg: int, wparam: int, lparam: int):
if msg != win32con.WM_DEVICECHANGE:
return 0
event, description =
self.WM_DEVICECHANGE_EVENTS[wparam]
logger.debug(f'Received message: {event} = {description}')
if event in ('DBT_DEVICEREMOVECOMPLETE',
'DBT_DEVICEARRIVAL'):
logger.info('A device has been plugged in (or out)')
self.on_change(self.list_drives())
return 0
@staticmethod
def list_drives() -> List[Drive]:
"""
Get a list of drives using WMI
:return: list of drives
"""
proc = subprocess.run(
args=[
'powershell',
'-noprofile',
'-command',
'Get-WmiObject -Class Win32_LogicalDisk | Select-
Object deviceid,volumename,drivetype | ConvertTo-Json'
],
text=True,
stdout=subprocess.PIPE
)
if proc.returncode != 0 or not proc.stdout.strip():
logger.error('Failed to enumerate drives')
return []
devices = json.loads(proc.stdout)
drive_types = {
0: 'Unknown',
1: 'No Root Directory',
2: 'Removable Disk',
3: 'Local Disk',
4: 'Network Drive',
5: 'Compact Disc',
6: 'RAM Disk',
}
return [Drive(
letter=d['deviceid'],
label=d['volumename'],
drive_type=drive_types[d['drivetype']]
) for d in devices]

def on_devices_changed(drives: List[Drive]):


removable_drives = [d for d in drives if d.is_removable]
logger.debug(f'Connected removable drives: {removable_drives}')
for drive in removable_drives:
backup(drive)

def backup(drive: Drive):


if drive.label != 'ABDUS':
return
logger.info('Backup drive has been plugged in')
logger.info(f'Backing up {drive.letter}')

if __name__ == '__main__':
listener = DeviceListener(on_change=on_devices_changed)
listener.start()

Quando o executamos e conectamos uma unidade, ele registra:


INFO:__main__:Listening to drive changes
DEBUG:__main__:Created listener window with hwnd=d60b86
DEBUG:__main__:Listening to messages
DEBUG:__main__:Received message: DBT_DEVNODES_CHANGED
= A device has been added to or removed from the system.
DEBUG:__main__:Received message: DBT_DEVNODES_CHANGED
= A device has been added to or removed from the system.
DEBUG:__main__:Received message: DBT_DEVICEARRIVAL = A
device or piece of media has been inserted and is now available.
INFO:__main__:A device has been plugged in (or out)
DEBUG:__main__:Connected removable drives: [Drive(letter='E:',
label='ABDUS', drive_type='Removable Disk')]
INFO:__main__:Backup drive has been plugged in
INFO:__main__:Backing up E:

Funciona 🙌.
Agora, o que você faz quando sua chamada de retorno é chamada é com
você. Você pode:
Copiar arquivos de / para a unidade
Faça backup de fotos quando um cartão SD for inserido
Exibir um pop-up para avisar o usuário
Trave o PC
Limpe a unidade (♂️
‍ você faz você)
...

Abordagem alternativa: polling para drives


Esta é minha tentativa mais antiga, em que sondo discos e aciono um
retorno de chamada quando o conjunto de unidades muda. É
consideravelmente mais simples, não depende pywin32e é mais fácil
de portar para outros sistemas operacionais (desde que você saiba como
listar unidades nessa plataforma).
import json
import subprocess
from dataclasses import dataclass
from typing import Callable, List

@dataclass
class Drive:
letter: str
label: str
drive_type: str
@property
def is_removable(self) -> bool:
return self.drive_type == 'Removable Disk'

def list_drives() -> List[Drive]:


"""
Get a list of drives using WMI
:return: list of drives
"""
proc = subprocess.run(
args=[
'powershell',
'-noprofile',
'-command',
'Get-WmiObject -Class Win32_LogicalDisk | Select-Object
deviceid,volumename,drivetype | ConvertTo-Json'
],
text=True,
stdout=subprocess.PIPE
)
if proc.returncode != 0 or not proc.stdout.strip():
print('Failed to enumerate drives')
return []
devices = json.loads(proc.stdout)
drive_types = {
0: 'Unknown',
1: 'No Root Directory',
2: 'Removable Disk',
3: 'Local Disk',
4: 'Network Drive',
5: 'Compact Disc',
6: 'RAM Disk',
}
return [Drive(
letter=d['deviceid'],
label=d['volumename'],
drive_type=drive_types[d['drivetype']]
) for d in devices]
def watch_drives(on_change: Callable[[List[Drive]], None],
poll_interval: int = 1):
prev = None
while True:
drives = list_drives()
if prev != drives:
on_change(drives)
prev = drives
sleep(poll_interval)

if __name__ == '__main__':
watch_drives(on_change=print)

Você pode fornecer seu próprio on_changeretorno de chamada e realizar


um trabalho se houver uma unidade.
Esta é a saída depois de conectar e desconectar um drive USB:
[Drive(letter='C:', label='', drive_type='Local Disk')]
[Drive(letter='C:', label='', drive_type='Local Disk'), Drive(letter='E:',
label='ABDUS', drive_type='Removable Disk')]
[Drive(letter='C:', label='', drive_type='Local Disk')]

You might also like