Created
August 3, 2022 18:26
-
-
Save tsbertalan/a9d854fc88d68633ea765abec0a26652 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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