Skip to content

Instantly share code, notes, and snippets.

@tsbertalan
Created August 3, 2022 18:26
Show Gist options
  • Save tsbertalan/a9d854fc88d68633ea765abec0a26652 to your computer and use it in GitHub Desktop.
Save tsbertalan/a9d854fc88d68633ea765abec0a26652 to your computer and use it in GitHub Desktop.
"""
Change the scrolling direction of a USB mouse.
"""
# Request UAC:
# https://newbedev.com/request-uac-elevation-from-within-a-python-script
import ctypes, sys
def is_admin():
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
if not is_admin():
# Re-run the program with admin rights
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)
exit(0)
import time, wmi, winreg
from tqdm.auto import tqdm
from pprint import pformat
# 1. List all attached USB mice.
def is_inputdev(item):
try:
return 'input' in item.Dependent.Caption.lower()
except wmi.x_wmi:
return False
class Device:
def __init__(self, wmi_item):
self.wmi_item = wmi_item
# print('Caption:', item.Dependent.Caption)
# print('DeviceID:', item.Dependent.DeviceID)
# print('PNPDeviceID:', item.Dependent.PNPDeviceID)
# print('Manufacturer:', item.Dependent.Manufacturer)
self.pnp_device_id = wmi_item.Dependent.PNPDeviceID
self.description = wmi_item.Dependent.Description
self.status = wmi_item.Dependent.Status
self.device_id = wmi_item.Dependent.DeviceID
self.manufacturer = wmi_item.Dependent.Manufacturer
# self.serial_number = wmi_item.Dependent.SerialNumber
def __str__(self):
return pformat(dict(
pnp_device_id=self.pnp_device_id,
description=self.description,
status=self.status,
device_id=self.device_id,
manufacturer=self.manufacturer,
# serial_number=self.serial_number,
))
class DeviceLister:
def __init__(self):
self.c = wmi.WMI()
self.wql = "Select * From Win32_USBControllerDevice"
def get_devices(self, filter_f=is_inputdev):
for item in self.c.query(self.wql):
if filter_f(item):
yield item
# print('Caption:', item.Dependent.Caption)
# print('DeviceID:', item.Dependent.DeviceID)
# print('PNPDeviceID:', item.Dependent.PNPDeviceID)
# print('Manufacturer:', item.Dependent.Manufacturer)
# print('----------------------------------------------------')
def watch_for_changes(self, n_seconds=4, interval=1, print_live=False, **kw_get):
items_seen_last_time = set(self.get_devices(**kw_get))
differences = set()
start_time = time.time()
end_time = start_time + n_seconds
steps = int(n_seconds // interval)
goal_step_times = [start_time + i * interval for i in range(steps)]
for t in tqdm(goal_step_times, desc='Watching (%s seconds)' % n_seconds):
# If we're past deadline, break the loop.
if t > end_time:
break
# Sleep 0 or more seconds, until the next goal time.
t = time.time()
if t < goal_step_times[0]:
time.sleep(goal_step_times[0] - t)
items_seen_now = set()
for item in self.get_devices(**kw_get):
items_seen_now.add(item)
# Any items currently in the set but not in the last time?
added = items_seen_now - items_seen_last_time
# Any items in the last time but not in the current time?
removed = items_seen_last_time - items_seen_now
if print_live:
if added:
print('='*50 + '\nAdded:')
for item in added:
print(Device(item))
print('----------------------------------------------------')
if removed:
print('='*50 + '\nRemoved:')
for item in removed:
print(item)
print('----------------------------------------------------')
# Any changes at all?
changes = added | removed
differences |= changes
items_seen_last_time = items_seen_now
if print_live and len(differences) > 0:
print('\n\nChanges:')
for item in differences:
print('----------------------------------------------------')
return sorted([
Device(item)
for item in differences
], key=lambda x: x.pnp_device_id)
lister = DeviceLister()
nsec = 12
print('Plug in (or unplug and replug) the input device in the next', nsec, 'seconds.')
all_differences = list(lister.watch_for_changes(n_seconds=nsec))
print('Found', len(all_differences), 'changed devices.')
# 2. Allow the user to select one.
print('\n\nSelect a device to change the scrolling direction:')
for i, item in enumerate(all_differences):
# Pad i+1 with spaces up to width 5.
numstr = str(i+1).ljust(5)
print(numstr)
padding = ' ' * len(numstr)
for line in str(item).split('\n'):
print(padding + line)
print('')
while True:
selection = input('Enter the number of the device you want to change: ')
try:
selection = int(selection)
selection -= 1
item = all_differences[selection]
break
except (ValueError, IndexError):
print('Invalid selection.')
print('Selected:')
# E.g. we see here:
# {'description': 'USB Input Device',
# 'device_id': 'USB\\VID_046D&PID_C08B&MI_00\\7&14E4BDBE&0&0000',
# 'manufacturer': '(Standard system devices)',
# 'pnp_device_id': 'USB\\VID_046D&PID_C08B&MI_00\\7&14E4BDBE&0&0000',
# 'status': 'OK'}
print(item)
try:
# 3. Get the VID*** of that mouse. E.g. VID_046D&PID_C08B&MI_00
vid = item.pnp_device_id.split('\\')[1]
print('VID:', vid)
# 4. Change \HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\HID\VID_046D&PID_C08B&MI_00\*\Device Parameters\FlipFlopWheel to 1
# (or 0 if it's already 1)
# List registry folders under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\HID\[VID FOUND]
registry_root = 'SYSTEM\\CurrentControlSet\\Enum\\HID\\' + vid
access_registry = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
registry_key = winreg.OpenKey(access_registry, registry_root)
registry_folders = [
winreg.EnumKey(registry_key, i)
for i in range(winreg.QueryInfoKey(registry_key)[0])
]
winreg.CloseKey(registry_key)
for folder in registry_folders:
new_path = registry_root + '\\' + folder + '\\Device Parameters\\'
item_name = 'FlipFlopWheel'
print('Opening item', 'Computer\\HKEY_LOCAL_MACHINE\\' + new_path + item_name)
access_registry = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
registry_key = winreg.OpenKey(access_registry, new_path)
value = winreg.QueryValueEx(registry_key, item_name)[0]
winreg.CloseKey(registry_key) # https://stackoverflow.com/questions/41870408/python-winreg-module-access-denied
target = int(not value)
print('Changing', item_name, 'from', value, 'to', target)
registry_key = winreg.OpenKey(access_registry, new_path, 0, winreg.KEY_WRITE)
winreg.SetValueEx(registry_key, item_name, 0, winreg.REG_DWORD, target)
winreg.CloseKey(registry_key)
# 5. Prompt the user to unplug, wait 5 seconds, and replug.
print('\n\nUnplug the device, wait 5 seconds, plug the device back in, and see if the scrolling direction changes.')
except Exception as e:
print('Error:', e)
n = 10
for sec in tqdm(range(n), desc='Waiting (%s seconds) before exit' % n):
time.sleep(1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment