Skip to content

Commit 680c47b

Browse files
committed
Ensure pythoncom is uninitialised properly after use
1 parent a9b1084 commit 680c47b

File tree

1 file changed

+117
-107
lines changed

1 file changed

+117
-107
lines changed

screen_brightness_control/windows.py

Lines changed: 117 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import logging
2+
import re
23
import threading
34
import time
5+
from contextlib import contextmanager
46
from ctypes import Structure, WinError, byref, windll
57
from ctypes.wintypes import BYTE, DWORD, HANDLE, WCHAR
68
from typing import List, Optional
@@ -10,7 +12,7 @@
1012
import win32api
1113
import win32con
1214
import wmi
13-
import re
15+
1416
from . import filter_monitors, get_methods
1517
from .exceptions import EDIDParseError, NoValidDisplayError, format_exc
1618
from .helpers import EDID, BrightnessMethod, __Cache, _monitor_brand_lookup
@@ -34,15 +36,21 @@
3436
'''
3537

3638

39+
@contextmanager
3740
def _wmi_init():
3841
'''internal function to create and return a wmi instance'''
3942
# WMI calls don't work in new threads so we have to run this check
40-
if threading.current_thread() != threading.main_thread():
43+
com_init = threading.current_thread() != threading.main_thread()
44+
if com_init:
4145
if COM_MODEL is None:
4246
pythoncom.CoInitialize()
4347
else:
4448
pythoncom.CoInitializeEx(COM_MODEL)
45-
return wmi.WMI(namespace='wmi')
49+
50+
yield wmi.WMI(namespace='wmi')
51+
52+
if com_init:
53+
pythoncom.CoUninitialize()
4654

4755

4856
def enum_display_devices() -> Generator[win32api.PyDISPLAY_DEVICEType, None, None]:
@@ -87,92 +95,92 @@ def get_display_info() -> List[dict]:
8795
monitor_uids[device.DeviceID.split('#')[2]] = device
8896

8997
# gather list of laptop displays to check against later
90-
wmi = _wmi_init()
91-
try:
92-
laptop_displays = [
93-
i.InstanceName
94-
for i in wmi.WmiMonitorBrightness()
95-
]
96-
except Exception as e:
97-
# don't do specific exception classes here because WMI does not play ball with it
98-
_logger.warning(
99-
f'get_display_info: failed to gather list of laptop displays - {format_exc(e)}')
100-
laptop_displays = []
101-
102-
extras, desktop, laptop = [], 0, 0
103-
uid_keys = list(monitor_uids.keys())
104-
for monitor in wmi.WmiMonitorDescriptorMethods():
105-
model, serial, manufacturer, man_id, edid = None, None, None, None, None
106-
instance_name = monitor.InstanceName.replace(
107-
'_0', '', 1).split('\\')[2]
108-
try:
109-
pydevice = monitor_uids[instance_name]
110-
except KeyError:
111-
# if laptop display WAS connected but was later put to sleep (#33)
112-
if instance_name in laptop_displays:
113-
laptop += 1
114-
else:
115-
desktop += 1
116-
_logger.warning(
117-
f'display {instance_name!r} is detected but not present in monitor_uids.'
118-
' Maybe it is asleep?'
119-
)
120-
continue
121-
122-
# get the EDID
98+
with _wmi_init() as wmi:
12399
try:
124-
edid = ''.join(
125-
f'{char:02x}' for char in monitor.WmiGetMonitorRawEEdidV1Block(0)[0])
126-
# we do the EDID parsing ourselves because calling wmi.WmiMonitorID
127-
# takes too long
128-
parsed = EDID.parse(edid)
129-
man_id, manufacturer, model, name, serial = parsed
130-
if name is None:
131-
raise EDIDParseError(
132-
'parsed EDID returned invalid display name')
133-
except EDIDParseError as e:
134-
edid = None
135-
_logger.warning(
136-
f'exception parsing edid str for {monitor.InstanceName} - {format_exc(e)}')
100+
laptop_displays = [
101+
i.InstanceName
102+
for i in wmi.WmiMonitorBrightness()
103+
]
137104
except Exception as e:
138-
edid = None
139-
_logger.error(
140-
f'failed to get EDID string for {monitor.InstanceName} - {format_exc(e)}')
141-
finally:
142-
if edid is None:
143-
devid = pydevice.DeviceID.split('#')
144-
serial = devid[2]
145-
man_id = devid[1][:3]
146-
model = devid[1][3:] or 'Generic Monitor'
147-
del devid
148-
if (brand := _monitor_brand_lookup(man_id)):
149-
man_id, manufacturer = brand
150-
151-
if (serial, model) != (None, None):
152-
data: dict = {
153-
'name': f'{manufacturer} {model}',
154-
'model': model,
155-
'serial': serial,
156-
'manufacturer': manufacturer,
157-
'manufacturer_id': man_id,
158-
'edid': edid,
159-
'uid': uid_match.group(1) if (uid_match := re.search(r"UID(\d+)", instance_name)) else None,
160-
}
161-
if monitor.InstanceName in laptop_displays:
162-
data['index'] = laptop
163-
data['method'] = WMI
164-
laptop += 1
165-
else:
166-
data['method'] = VCP
167-
desktop += 1
168-
169-
if instance_name in uid_keys:
170-
# insert the data into the uid_keys list because
171-
# uid_keys has the monitors sorted correctly. This
172-
# means we don't have to re-sort the list later
173-
uid_keys[uid_keys.index(instance_name)] = data
174-
else:
175-
extras.append(data)
105+
# don't do specific exception classes here because WMI does not play ball with it
106+
_logger.warning(
107+
f'get_display_info: failed to gather list of laptop displays - {format_exc(e)}')
108+
laptop_displays = []
109+
110+
extras, desktop, laptop = [], 0, 0
111+
uid_keys = list(monitor_uids.keys())
112+
for monitor in wmi.WmiMonitorDescriptorMethods():
113+
model, serial, manufacturer, man_id, edid = None, None, None, None, None
114+
instance_name = monitor.InstanceName.replace(
115+
'_0', '', 1).split('\\')[2]
116+
try:
117+
pydevice = monitor_uids[instance_name]
118+
except KeyError:
119+
# if laptop display WAS connected but was later put to sleep (#33)
120+
if instance_name in laptop_displays:
121+
laptop += 1
122+
else:
123+
desktop += 1
124+
_logger.warning(
125+
f'display {instance_name!r} is detected but not present in monitor_uids.'
126+
' Maybe it is asleep?'
127+
)
128+
continue
129+
130+
# get the EDID
131+
try:
132+
edid = ''.join(
133+
f'{char:02x}' for char in monitor.WmiGetMonitorRawEEdidV1Block(0)[0])
134+
# we do the EDID parsing ourselves because calling wmi.WmiMonitorID
135+
# takes too long
136+
parsed = EDID.parse(edid)
137+
man_id, manufacturer, model, name, serial = parsed
138+
if name is None:
139+
raise EDIDParseError(
140+
'parsed EDID returned invalid display name')
141+
except EDIDParseError as e:
142+
edid = None
143+
_logger.warning(
144+
f'exception parsing edid str for {monitor.InstanceName} - {format_exc(e)}')
145+
except Exception as e:
146+
edid = None
147+
_logger.error(
148+
f'failed to get EDID string for {monitor.InstanceName} - {format_exc(e)}')
149+
finally:
150+
if edid is None:
151+
devid = pydevice.DeviceID.split('#')
152+
serial = devid[2]
153+
man_id = devid[1][:3]
154+
model = devid[1][3:] or 'Generic Monitor'
155+
del devid
156+
if (brand := _monitor_brand_lookup(man_id)):
157+
man_id, manufacturer = brand
158+
159+
if (serial, model) != (None, None):
160+
data: dict = {
161+
'name': f'{manufacturer} {model}',
162+
'model': model,
163+
'serial': serial,
164+
'manufacturer': manufacturer,
165+
'manufacturer_id': man_id,
166+
'edid': edid,
167+
'uid': uid_match.group(1) if (uid_match := re.search(r"UID(\d+)", instance_name)) else None,
168+
}
169+
if monitor.InstanceName in laptop_displays:
170+
data['index'] = laptop
171+
data['method'] = WMI
172+
laptop += 1
173+
else:
174+
data['method'] = VCP
175+
desktop += 1
176+
177+
if instance_name in uid_keys:
178+
# insert the data into the uid_keys list because
179+
# uid_keys has the monitors sorted correctly. This
180+
# means we don't have to re-sort the list later
181+
uid_keys[uid_keys.index(instance_name)] = data
182+
else:
183+
extras.append(data)
176184

177185
info = uid_keys + extras
178186
if desktop:
@@ -205,21 +213,23 @@ def get_display_info(cls, display: Optional[DisplayIdentifier] = None) -> List[d
205213

206214
@classmethod
207215
def set_brightness(cls, value: IntPercentage, display: Optional[int] = None):
208-
brightness_method = _wmi_init().WmiMonitorBrightnessMethods()
209-
if display is not None:
210-
brightness_method = [brightness_method[display]]
216+
with _wmi_init() as wmi:
217+
brightness_method = wmi.WmiMonitorBrightnessMethods()
218+
if display is not None:
219+
brightness_method = [brightness_method[display]]
211220

212-
for method in brightness_method:
213-
method.WmiSetBrightness(value, 0)
221+
for method in brightness_method:
222+
method.WmiSetBrightness(value, 0)
214223

215224
@classmethod
216225
def get_brightness(cls, display: Optional[int] = None) -> List[IntPercentage]:
217-
brightness_method = _wmi_init().WmiMonitorBrightness()
218-
if display is not None:
219-
brightness_method = [brightness_method[display]]
226+
with _wmi_init() as wmi:
227+
brightness_method = wmi.WmiMonitorBrightness()
228+
if display is not None:
229+
brightness_method = [brightness_method[display]]
220230

221-
values = [i.CurrentBrightness for i in brightness_method]
222-
return values
231+
values = [i.CurrentBrightness for i in brightness_method]
232+
return values
223233

224234

225235
class VCP(BrightnessMethod):
@@ -250,16 +260,16 @@ def iter_physical_monitors(cls, start: int = 0) -> Generator[HANDLE, None, None]
250260
monitor_index = 0
251261
display_devices = list(enum_display_devices())
252262

253-
wmi = _wmi_init()
254-
try:
255-
laptop_displays = [
256-
i.InstanceName.replace('_0', '').split('\\')[2]
257-
for i in wmi.WmiMonitorBrightness()
258-
]
259-
except Exception as e:
260-
cls._logger.warning(
261-
f'failed to gather list of laptop displays - {format_exc(e)}')
262-
laptop_displays = []
263+
with _wmi_init() as wmi:
264+
try:
265+
laptop_displays = [
266+
i.InstanceName.replace('_0', '').split('\\')[2]
267+
for i in wmi.WmiMonitorBrightness()
268+
]
269+
except Exception as e:
270+
cls._logger.warning(
271+
f'failed to gather list of laptop displays - {format_exc(e)}')
272+
laptop_displays = []
263273

264274
for monitor in map(lambda m: m[0].handle, win32api.EnumDisplayMonitors()):
265275
# Get physical monitor count

0 commit comments

Comments
 (0)