Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pull request #414

Draft
wants to merge 60 commits into
base: develop
Choose a base branch
from
Draft
Changes from 1 commit
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
e6640c8
Add Windows testing to GitHub Actions
oldgithubman Oct 13, 2024
f384db5
Refactor and improve type annotations in mocks.py
oldgithubman Oct 13, 2024
d981a17
Update AGW package documentation to version 0.9.7
oldgithubman Oct 13, 2024
d141daf
Refactor and enhance AGW artmanager module
oldgithubman Oct 13, 2024
0c917df
Add bold tab selection style and improve type hints
oldgithubman Oct 13, 2024
307a1a2
Update FourWaySplitter for better documentation and Python 3 compatib…
oldgithubman Oct 13, 2024
b4e65a6
Update GradientButton for Python 3 and Phoenix
oldgithubman Oct 13, 2024
90851e5
Update LabelBook and FlatImageBook documentation
oldgithubman Oct 13, 2024
3b94aa2
Update PyGauge to support drawing value or percentage
oldgithubman Oct 13, 2024
c5cfcc8
Refactor and improve audio module documentation
oldgithubman Oct 13, 2024
e382e2c
Refactor and improve code readability in config.py
oldgithubman Oct 14, 2024
3ac2eb6
Update win32com imports to win32comext
oldgithubman Oct 14, 2024
ddc0696
Refactor imports and improve readability
oldgithubman Oct 14, 2024
c6dd4bb
Update flake8 config for stricter checks
oldgithubman Oct 14, 2024
5a39997
Reorganize and update .gitignore file
oldgithubman Oct 14, 2024
bd8ac7a
Update requirements.txt with new dependencies
oldgithubman Oct 14, 2024
3101b65
Add type hints to ArtManager.Initialize
oldgithubman Oct 14, 2024
e614e63
Refactor and document EDID retrieval and parsing
oldgithubman Oct 14, 2024
4d75b98
Refactor and clean up freeze.py script
oldgithubman Oct 14, 2024
83eec46
Refactor imports and Python version check
oldgithubman Oct 14, 2024
cf9dc8c
Fix formatting and minor typos in pnp.ids
oldgithubman Oct 14, 2024
69421d6
Refactor imports and improve readability in profile_loader.py
oldgithubman Oct 14, 2024
77f82ef
Refactor and improve RealDisplaySizeMM.py
oldgithubman Oct 14, 2024
72eeeb7
Refactor and enhance AGW artmanager module
oldgithubman Oct 13, 2024
9648079
Refactor and improve code readability in config.py
oldgithubman Oct 14, 2024
c1de081
Refactor imports and improve readability
oldgithubman Oct 14, 2024
24951ea
Refactor and document EDID retrieval and parsing
oldgithubman Oct 14, 2024
00a5a9a
- [#414] Fixed `DisplayCAL.lib.agw.artmanager` for MacOS (and possibl…
eoyilmaz Oct 14, 2024
e49641e
Refactor taskbar progress using ctypes for ITaskbarList3
oldgithubman Oct 14, 2024
51b663d
- [#414] Trying to fix Windows workflow (1).
eoyilmaz Oct 14, 2024
2d5291b
Format taskbar.py
oldgithubman Oct 14, 2024
2bfb3a4
Refactor and enhance util_os.py for better readability and functionality
oldgithubman Oct 14, 2024
39fdba8
Add docstrings and improve code readability
oldgithubman Oct 14, 2024
52698ac
- [#414] Trying to fix Windows workflow (2).
eoyilmaz Oct 14, 2024
35693df
Reorganize imports and fix indentation
oldgithubman Oct 14, 2024
acb860f
Refactor imports in wxCCXXPlot.py for clarity and consistency
oldgithubman Oct 14, 2024
e546a57
Refactor imports for better readability and organization
oldgithubman Oct 14, 2024
bd55ea5
Refactor imports in wxLUTViewer.py for clarity and organization
oldgithubman Oct 14, 2024
c80a73e
Refactor imports in wxMeasureFrame.py for clarity
oldgithubman Oct 14, 2024
9666556
Reorganize imports for better readability and structure
oldgithubman Oct 15, 2024
a78b04e
Refactor imports in wxScriptingClient.py
oldgithubman Oct 15, 2024
43242b3
Refactor imports in wxSynthICCFrame.py
oldgithubman Oct 15, 2024
7bb3e09
Refactor imports and improve readability
oldgithubman Oct 15, 2024
59b30ad
Refactor imports and improve code readability
oldgithubman Oct 15, 2024
bc4a3dc
Refactor and improve comments in tests/conftest.py
oldgithubman Oct 15, 2024
7e90ba2
Update test_colord.py to use platform module and fix import order
oldgithubman Oct 15, 2024
a945d1e
Update tests and dependencies for Python 3.11
oldgithubman Oct 15, 2024
96ef5af
Add debugging print statements to test_ICCProfile.py
oldgithubman Oct 15, 2024
feda88d
Convert AGW package docstring to use double quotes
oldgithubman Oct 15, 2024
3afeea1
Refactor and update project configuration files
oldgithubman Oct 16, 2024
84f7f94
Delete .flake8 and update .gitignore
oldgithubman Oct 16, 2024
105cd7d
Refactor and improve FourWaySplitter documentation and types
oldgithubman Oct 18, 2024
80a2842
Refactor GradientButton for better type hints and readability
oldgithubman Oct 18, 2024
f00706f
Refactor and improve LabelBook and FlatImageBook
oldgithubman Oct 23, 2024
561e825
[#414] Updated `.github/workflows/pytest.yml` to disable C-Extension …
eoyilmaz Oct 24, 2024
eabc81b
[#414] Removed all the `# noqa: SC100` comments as it is better to ad…
eoyilmaz Oct 24, 2024
23f40ad
[#414] Refactored the `DisplayCAL.lib.agw.artmanager`.
eoyilmaz Oct 24, 2024
b433eba
[#414] Refactored the `DisplayCAL.lib.agw.fmresources`.
eoyilmaz Oct 24, 2024
3f3d66f
[#414] Fixed most of the linting errors in `DisplayCAL.lib.agw.artman…
eoyilmaz Oct 24, 2024
ea2febe
[#414] Introduced some more style changes to `fourwaysplitter` and `g…
eoyilmaz Oct 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add docstrings and improve code readability
  - Add docstrings to functions
  - Improve code formatting
  - Use f-strings for better readability
  - Add type hints and parameter descriptions
  - Fix comments and inline documentation
oldgithubman committed Oct 23, 2024
commit 39fdba85f45918b5dbed34932dfc13e75c0f8716
384 changes: 297 additions & 87 deletions DisplayCAL/util_win.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
# -*- coding: utf-8 -*-
"""
util_win.py.
from ctypes import wintypes
This module provides utility functions for interacting with Windows-specific features,
such as display devices, process management, and color management.
"""
import ctypes
import _ctypes
import winreg
import platform
import struct
import sys
from ctypes import POINTER, byref, sizeof, windll, wintypes
from ctypes.wintypes import DWORD, HANDLE, LPWSTR

from DisplayCAL.util_os import quote_args
from DisplayCAL.win_structs import UNICODE_STRING

import _ctypes

import pywintypes

import win32api

from win32comext.shell import shell as win32com_shell

import win32con

import win32process
import winerror
from win32com.shell import shell as win32com_shell

from ctypes import POINTER, byref, sizeof, windll
from ctypes.wintypes import HANDLE, DWORD, LPWSTR
import winerror

from DisplayCAL.util_os import quote_args
from DisplayCAL.win_structs import UNICODE_STRING
import winreg

if not hasattr(ctypes, "c_bool"):
# Python 2.5
@@ -39,20 +49,23 @@
psapi = None


# Access registry directly instead of Wcs* functions that leak handles
# Access registry directly instead of Wcs* functions that leak handles # noqa: SC100
USE_REGISTRY = True


# DISPLAY_DEVICE structure, StateFlags member
# http://msdn.microsoft.com/en-us/library/dd183569%28v=vs.85%29.aspx

# wingdi.h
# wingdi.h # noqa: SC100

# Flags for parent devices
DISPLAY_DEVICE_ATTACHED_TO_DESKTOP = 0x1
DISPLAY_DEVICE_MIRRORING_DRIVER = 0x8 # Represents a pseudo device used to mirror application drawing for remoting or other purposes.
DISPLAY_DEVICE_MIRRORING_DRIVER = 0x8
# Represents a pseudo device used to mirror application drawing for remoting or # noqa: SC100
# other purposes.
# An invisible pseudo monitor is associated with this device.
# For example, NetMeeting uses it. Note that GetSystemMetrics (SM_MONITORS) only accounts for visible display monitors.
# For example, NetMeeting uses it.
# Note that GetSystemMetrics (SM_MONITORS) only accounts for visible display monitors.
DISPLAY_DEVICE_MODESPRUNED = (
0x8000000 # The device has more display modes than its output devices support.
)
@@ -68,16 +81,19 @@
DISPLAY_DEVICE_REMOTE = 0x4000000

# Flags for child devices
DISPLAY_DEVICE_ACTIVE = 0x1 # DISPLAY_DEVICE_ACTIVE specifies whether a monitor is presented as being "on" by the respective GDI view.
# Windows Vista: EnumDisplayDevices will only enumerate monitors that can be presented as being "on."
DISPLAY_DEVICE_ACTIVE = 0x1
# DISPLAY_DEVICE_ACTIVE specifies whether a monitor is presented as being "on"
# by the respective GDI view. # noqa: SC100
# Windows Vista:
# EnumDisplayDevices will only enumerate monitors that can be presented as being "on."
DISPLAY_DEVICE_ATTACHED = 0x2


# MONITORINFO structure, dwFlags member
# MONITORINFO structure, dwFlags member # noqa: SC100
# https://msdn.microsoft.com/de-de/library/windows/desktop/dd145065(v=vs.85).aspx
MONITORINFOF_PRIMARY = 0x1

# Icm.h
# Icm.h # noqa: SC100
CLASS_MONITOR = struct.unpack("!L", b"mntr")[0]
CLASS_PRINTER = struct.unpack("!L", b"prtr")[0]
CLASS_SCANNER = struct.unpack("!L", b"scnr")[0]
@@ -106,22 +122,37 @@ def _get_icm_display_device_key(devicekey):
return winreg.CreateKey(winreg.HKEY_CURRENT_USER, subkey)


def _get_mscms_windll():
try:
if not _get_mscms_windll._windll:
_get_mscms_windll._windll = MSCMS()
return _get_mscms_windll._windll
except WindowsError:
return None
class MSCMSLoader:
"""Loader class for MSCMS."""

_windll = None

_get_mscms_windll._windll = None
@classmethod
def get_mscms_windll(cls):
"""
Get the MSCMS windll instance.
Returns:
MSCMS: The MSCMS windll instance.
"""
if not cls._windll:
cls._windll = MSCMS()
return cls._windll


def _get_mscms_windll():
return MSCMSLoader.get_mscms_windll()


def calibration_management_isenabled():
"""Check if calibration is enabled under Windows 7"""
"""
Check if calibration is enabled under Windows 7.
Returns:
bool: True if calibration is enabled, False otherwise.
"""
if sys.getwindowsversion() < (6, 1):
# Windows XP and Vista don't have calibration management
# Windows XP and Vista don't have calibration management # noqa: SC100
return False
if False:
# Using registry - NEVER
@@ -141,20 +172,38 @@ def calibration_management_isenabled():


def disable_calibration_management():
"""Disable calibration loading under Windows 7"""
"""Disable calibration loading under Windows 7."""
enable_calibration_management(False)


def disable_per_user_profiles(display_no=0):
"""Disable per user profiles under Vista/Windows 7"""
"""
Disable per user profiles under Vista/Windows 7.
Args:
display_no (int): The display number.
"""
enable_per_user_profiles(False, display_no)


def enable_calibration_management(enable=True):
"""Enable calibration loading under Windows 7"""
"""
Enable calibration loading under Windows 7.
Args:
enable (bool): Whether to enable calibration management.
Returns:
bool: True if successful, False otherwise.
Raises:
NotImplementedError: If the OS version is less than Windows 7.
get_windows_error: If an error occurs while setting the calibration
management state.
"""
if sys.getwindowsversion() < (6, 1):
raise NotImplementedError(
"Calibration Management is only available " "in Windows 7 or later"
"Calibration Management is only available in Windows 7 or later"
)
if False:
# Using registry - NEVER
@@ -178,11 +227,26 @@ def enable_calibration_management(enable=True):


def enable_per_user_profiles(enable=True, display_no=0, devicekey=None):
"""Enable per user profiles under Vista/Windows 7"""
"""
Enable per user profiles under Vista/Windows 7.
Args:
enable (bool): Whether to enable per user profiles.
display_no (int): The display number.
devicekey (str): The device key.
Returns:
bool: True if successful, False otherwise.
Raises:
NotImplementedError: If the OS version is less than Windows Vista.
get_windows_error: If an error occurs while setting the per user
profiles state.
"""
if sys.getwindowsversion() < (6,):
# Windows XP doesn't have per-user profiles
# Windows XP doesn't have per-user profiles # noqa: SC100
raise NotImplementedError(
"Per-user profiles are only available " "in Windows Vista, 7 or later"
"Per-user profiles are only available in Windows Vista, 7 or later"
)
if not devicekey:
device = get_display_device(display_no)
@@ -196,7 +260,7 @@ def enable_per_user_profiles(enable=True, display_no=0, devicekey=None):
)
else:
# Using ctypes - this leaks registry key handles internally in
# WcsSetUsePerUserProfiles since Windows 10 1903
# WcsSetUsePerUserProfiles since Windows 10 1903 # noqa: SC100
mscms = _get_mscms_windll()
if not mscms:
return False
@@ -208,14 +272,18 @@ def enable_per_user_profiles(enable=True, display_no=0, devicekey=None):


def get_display_devices(devicename):
"""Get all display devices of an output (there can be several)
Return value: list of display devices
r"""
Get all display devices of an output (there can be several).
Example usage:
get_display_devices('\\\\.\\DISPLAY1')
devicename = '\\\\.\\DISPLAYn' where n is a positive integer starting at 1
get_display_devices('\\\\.\\DISPLAY1')
devicename = '\\\\.\\DISPLAYn' where n is a positive integer starting at 1.
Args:
devicename (str): The device name.
Returns:
list: List of display devices.
"""
devices = []
n = 0
@@ -229,22 +297,38 @@ def get_display_devices(devicename):


def get_first_display_device(devicename, exception_cls=pywintypes.error):
"""Get the first display of device <devicename>."""
"""
Get the first display of device <devicename>.
Args:
devicename (str): The device name.
exception_cls (Exception): The exception class to catch.
Returns:
DisplayDevice: The first display device.
"""
try:
return win32api.EnumDisplayDevices(devicename, 0)
except exception_cls:
pass


def get_active_display_device(devicename, devices=None):
"""Get active display device of an output (there can only be one per output)
r"""
Get active display device of an output (there can only be one per output).
Return value: display device object or None
Return value: display device object or None.
Example usage:
get_active_display_device('\\\\.\\DISPLAY1')
devicename = '\\\\.\\DISPLAYn' where n is a positive integer starting at 1
get_active_display_device('\\\\.\\DISPLAY1')
devicename = '\\\\.\\DISPLAYn' where n is a positive integer starting at 1.
Args:
devicename (str): The device name.
devices (list): List of devices.
Returns:
DisplayDevice: The active display device (display device object) or None.
"""
if not devices:
devices = get_display_devices(devicename)
@@ -256,7 +340,15 @@ def get_active_display_device(devicename, devices=None):


def get_active_display_devices(attrname=None):
"""Return active display devices"""
"""
Return active display devices.
Args:
attrname (str): The attribute name to get from the display device.
Returns:
list: List of active display devices.
"""
devices = []
for moninfo in get_real_display_devices_info():
device = get_active_display_device(moninfo["Device"])
@@ -270,8 +362,18 @@ def get_active_display_devices(attrname=None):
def get_display_device(
display_no=0, use_active_display_device=False, exception_cls=pywintypes.error
):
# The ordering will work as long as Argyll continues using
# EnumDisplayMonitors
"""
Get the display device for a given display number.
Args:
display_no (int): The display number.
use_active_display_device (bool): Whether to use the active display device.
exception_cls (Exception): The exception class to catch.
Returns:
DisplayDevice: The display device.
"""
# The ordering will work as long as Argyll continues using EnumDisplayMonitors # noqa: SC100
monitors = get_real_display_devices_info()
moninfo = monitors[display_no]
if use_active_display_device:
@@ -281,6 +383,19 @@ def get_display_device(


def get_process_filename(pid, handle=0):
"""
Get the filename of a process.
Args:
pid (int): The process ID.
handle (int): The process handle.
Returns:
str: The filename of the process.
Raises:
WinError: If an error occurs while querying the process filename.
"""
if sys.getwindowsversion() >= (6,):
flags = PROCESS_QUERY_LIMITED_INFORMATION
else:
@@ -291,7 +406,7 @@ def get_process_filename(pid, handle=0):
if sys.getwindowsversion() >= (6,):
dwSize = win32con.MAX_PATH
while True:
dwFlags = 0 # The name should use the Win32 path format
dwFlags = 0 # The name should use the Win32 path format # noqa: SC100
lpdwSize = DWORD(dwSize)
lpExeName = ctypes.create_unicode_buffer("", lpdwSize.value + 1)
success = QueryFullProcessImageNameW(
@@ -317,16 +432,21 @@ def get_process_filename(pid, handle=0):


def get_file_info(filename):
"""Get exe/dll file information"""
"""
Get exe/dll file information.
Args:
filename (str): The filename.
Returns:
dict: The file information.
"""
info = {"FileInfo": None, "StringFileInfo": {}, "FileVersion": None}

finfo = win32api.GetFileVersionInfo(filename, "\\")
info["FileInfo"] = finfo
info["FileVersion"] = "%i.%i.%i.%i" % (
finfo["FileVersionMS"] / 65536,
finfo["FileVersionMS"] % 65536,
finfo["FileVersionLS"] / 65536,
finfo["FileVersionLS"] % 65536,
info["FileVersion"] = (
f"{finfo['FileVersionMS'] // 65536}.{finfo['FileVersionMS'] % 65536}.{finfo['FileVersionLS'] // 65536}.{finfo['FileVersionLS'] % 65536}" # noqa: B950
)
for lcid, codepage in win32api.GetFileVersionInfo(
filename, "\\VarFileInfo\\Translation"
@@ -347,7 +467,7 @@ def get_file_info(filename):
"SpecialBuild",
]:
value = win32api.GetFileVersionInfo(
filename, "\\StringFileInfo\\%04X%04X\\%s" % (lcid, codepage, name)
filename, f"\\StringFileInfo\\{lcid:04X}{codepage:04X}\\{name}"
)
if value is not None:
info["StringFileInfo"][lcid, codepage][name] = value
@@ -356,7 +476,21 @@ def get_file_info(filename):


def get_pids():
"""Get PIDs of all running processes"""
"""
Get PIDs of all running processes.
Returns:
list: List of PIDs.
Raises:
ImportError: If the psapi module is not available.
get_windows_error: If an error occurs while enumerating processes.
"""
if psapi is None:
raise ImportError(
"psapi module is not available. Please ensure it is installed and accessible." # noqa: B950
)

pids_count = 1024
while True:
pids = (DWORD * pids_count)()
@@ -372,12 +506,19 @@ def get_pids():


def get_real_display_devices_info():
"""Return info for real (non-virtual) devices"""
# See Argyll source spectro/dispwin.c MonitorEnumProc, get_displays
"""
Return info for real (non-virtual) devices.
Returns:
list: List of monitor info.
"""
# See Argyll source spectro/dispwin.c MonitorEnumProc, get_displays # noqa: SC100
monitors = []
for monitor in win32api.EnumDisplayMonitors(None, None):
try:
moninfo = win32api.GetMonitorInfo(monitor[0])
# Convert PyHANDLE to int using the .handle attribute
hMonitor = monitor[0].handle
moninfo = win32api.GetMonitorInfo(hMonitor)
except pywintypes.error:
pass
else:
@@ -387,13 +528,34 @@ def get_real_display_devices_info():


def get_windows_error(errorcode):
"""
Get a Windows error message.
Args:
errorcode (int): The error code.
Returns:
ctypes.WinError: The Windows error message.
"""
return ctypes.WinError(errorcode)


def per_user_profiles_isenabled(display_no=0, devicekey=None):
"""Check if per user profiles is enabled under Vista/Windows 7"""
"""
Check if per user profiles is enabled under Vista/Windows 7.
Args:
display_no (int): The display number.
devicekey (str): The device key.
Returns:
bool: True if per user profiles is enabled, False otherwise.
Raises:
WindowsError: If an error occurs while querying the registry.
"""
if sys.getwindowsversion() < (6,):
# Windows XP doesn't have per-user profiles
# Windows XP doesn't have per-user profiles # noqa: SC100
return False
if not devicekey:
device = get_display_device(display_no)
@@ -410,7 +572,7 @@ def per_user_profiles_isenabled(display_no=0, devicekey=None):
raise
else:
# Using ctypes - this leaks registry key handles internally in
# WcsGetUsePerUserProfiles since Windows 10 1903
# WcsGetUsePerUserProfiles since Windows 10 1903 # noqa: SC100
mscms = _get_mscms_windll()
pbool = ctypes.pointer(ctypes.c_bool())
if not mscms or not mscms.WcsGetUsePerUserProfiles(
@@ -423,12 +585,21 @@ def per_user_profiles_isenabled(display_no=0, devicekey=None):
def run_as_admin(
cmd, args, close_process=True, async_=False, wait_for_idle=False, show=True
):
"""Run command with elevated privileges.
"""
Run command with elevated privileges.
This is a wrapper around ShellExecuteEx.
Returns a dictionary with hInstApp and hProcess members.
Args:
cmd (str): The command to run.
args (list): The arguments for the command.
close_process (bool): Whether to close the process after execution.
async_ (bool): Whether to run the command asynchronously.
wait_for_idle (bool): Whether to wait for the process to be idle.
show (bool): Whether to show the command window.
Returns:
dict: A dictionary with hInstApp and hProcess members.
"""
return shell_exec(cmd, args, "runas", close_process, async_, wait_for_idle, show)

@@ -442,12 +613,22 @@ def shell_exec(
wait_for_idle=False,
show=True,
):
"""Run command.
"""
Run command.
This is a wrapper around ShellExecuteEx.
Returns a dictionary with hInstApp and hProcess members.
Args:
filename (str): The filename to execute.
args (list): The arguments for the command.
operation (str): The operation to perform.
close_process (bool): Whether to close the process after execution.
async_ (bool): Whether to run the command asynchronously.
wait_for_idle (bool): Whether to wait for the process to be idle.
show (bool): Whether to show the command window.
Returns:
dict: A dictionary with hInstApp and hProcess members.
"""
flags = SEE_MASK_FLAG_NO_UI
if not close_process:
@@ -467,7 +648,12 @@ def shell_exec(


def win_ver():
"""Get Windows version info"""
"""
Get Windows version info.
Returns:
tuple: A tuple containing the product name, CSD version, release, and build.
"""
csd = sys.getwindowsversion()[-1]
# Use the registry to get product name, e.g. 'Windows 7 Ultimate'.
# Not recommended, but we don't care.
@@ -486,10 +672,10 @@ def win_ver():
sam,
)
pname = winreg.QueryValueEx(key, "ProductName")[0]
build = "Build %s" % winreg.QueryValueEx(key, "CurrentBuildNumber")[0]
build = f"Build {winreg.QueryValueEx(key, 'CurrentBuildNumber')[0]}"
# Since Windows 10
release = "Version %s" % winreg.QueryValueEx(key, "ReleaseId")[0]
build += ".%s" % winreg.QueryValueEx(key, "UBR")[0]
release = f"Version {winreg.QueryValueEx(key, 'ReleaseId')[0]}"
build += f".{winreg.QueryValueEx(key, 'UBR')[0]}"
except Exception:
pass
finally:
@@ -510,69 +696,93 @@ def _free_library(handle):


class UnloadableWinDLL(object):
"""WinDLL wrapper that allows unloading"""
"""WinDLL wrapper that allows unloading."""

def __init__(self, dllname):
self.dllname = dllname
self._windll = None
self.load()

def __getattr__(self, name):
"""
Get an attribute from the loaded DLL.
Args:
name (str): The name of the attribute.
Returns:
The attribute from the loaded DLL.
"""
self.load()
return getattr(self._windll, name)

def __bool__(self):
"""
Check if the DLL is loaded.
Returns:
bool: True if the DLL is loaded, False otherwise.
"""
self.load()
return bool(self._windll)

def load(self):
"""Load the DLL."""
if not self._windll:
if USE_NTDLL_LDR:
mod = wintypes.byref(
UNICODE_STRING(len(self.dllname) * 2, 256, self.dllname)
)
mod = byref(UNICODE_STRING(len(self.dllname) * 2, 256, self.dllname))
handle = wintypes.HANDLE()
ctypes.windll.ntdll.LdrLoadDll(None, 0, mod, wintypes.byref(handle))
ctypes.windll.ntdll.LdrLoadDll(None, 0, mod, byref(handle))
windll = ctypes.WinDLL(self.dllname, handle=handle.value)
else:
windll = ctypes.WinDLL(self.dllname)
self._windll = windll

def unload(self):
"""Unload the DLL."""
if self._windll:
handle = self._windll._handle
self._windll = None
_free_library(handle)


class MSCMS(UnloadableWinDLL):
"""MSCMS wrapper (optionally) allowing unloading"""
"""MSCMS wrapper (optionally) allowing unloading."""

def __init__(self, bootstrap_icm32=False):
self._icm32_handle = None
UnloadableWinDLL.__init__(self, "mscms.dll")
if bootstrap_icm32:
# Need to load & unload icm32 once before unloading of mscms can
# work in every situation (some calls to mscms methods pull in
# icm32, if we haven't loaded/unloaded it before, we won't be able
# to unload then)
# Need to load & unload icm32 once before unloading of mscms # noqa: SC100
# can work in every situation
# (some calls to mscms methods pull in icm32, # noqa: SC100
# if we haven't loaded/unloaded it before, we won't be able to unload then)
self._icm32_handle = ctypes.WinDLL("icm32")._handle
_free_library(self._icm32_handle)

def load(self):
"""Load the MSCMS DLL."""
mscms = self._windll
UnloadableWinDLL.load(self)
if self._windll is not mscms:
mscms = self._windll
mscms.WcsGetDefaultColorProfileSize.restype = ctypes.c_bool
mscms.WcsGetDefaultColorProfile.restype = ctypes.c_bool
mscms.WcsAssociateColorProfileWithDevice.restype = ctypes.c_bool
mscms.WcsDisassociateColorProfileFromDevice.restype = ctypes.c_bool
if (
mscms
): # Ensure mscms is not None # noqa: SC100
mscms.WcsGetDefaultColorProfileSize.restype = ctypes.c_bool
mscms.WcsGetDefaultColorProfile.restype = ctypes.c_bool
mscms.WcsAssociateColorProfileWithDevice.restype = ctypes.c_bool
mscms.WcsDisassociateColorProfileFromDevice.restype = ctypes.c_bool

def unload(self):
"""Unload the MSCMS DLL.
Raises:
WindowsError: If an error occurs while unloading the DLL.
"""
if self._windll:
if self._icm32_handle:
# Need to free icm32 first, otherwise mscms won't unload
# Need to free icm32 first, otherwise mscms won't unload # noqa: SC100
try:
_free_library(self._icm32_handle)
except WindowsError as exception: