Skip to content

Commit

Permalink
Separate HDR and resolution switches into mutually exclusive settings.
Browse files Browse the repository at this point in the history
This means that when executing the program, you can only execute an HDR change, or resolution change.
  • Loading branch information
Andre Bocchini committed Sep 5, 2024
1 parent f8c135b commit d9c75e8
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 52 deletions.
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,31 @@ These examples assume the application is installed at `C:\Program Files\Resoluti
## Do Commands

```shell
cmd /C "C:\Program Files\ResolutionSwitcher\ResolutionSwitcher.exe" --width %SUNSHINE_CLIENT_WIDTH% --height %SUNSHINE_CLIENT_HEIGHT% --refresh %SUNSHINE_CLIENT_FPS% --hdr %SUNSHINE_CLIENT_HDR%
cmd /C "C:\Program Files\ResolutionSwitcher\ResolutionSwitcher.exe" --width %SUNSHINE_CLIENT_WIDTH% --height %SUNSHINE_CLIENT_HEIGHT% --refresh %SUNSHINE_CLIENT_FPS%
```

```shell
cmd /C "C:\Program Files\ResolutionSwitcher\ResolutionSwitcher.exe" --hdr %SUNSHINE_CLIENT_HDR%
```

## Undo Commands

```shell
cmd /C "C:\Program Files\ResolutionSwitcher\ResolutionSwitcher.exe" --width 3840 --height 2160 --refresh 144 --hdr false
cmd /C "C:\Program Files\ResolutionSwitcher\ResolutionSwitcher.exe" --width 3840 --height 2160 --refresh 144
```

```shell
cmd /C "C:\Program Files\ResolutionSwitcher\ResolutionSwitcher.exe" --hdr false
```

# Building

The tool is written in [Python](https://www.python.org/) and uses the [ctypes](https://docs.python.org/3/library/ctypes.html) library to interact with the Windows API.

For distribution, the Python script is compiled into an executable using [`pyinstaller`](https://www.pyinstaller.org/).

# Known Issues

Sometimes when changing HDR state, the screen resolution may reset to a previous
resolution. As far as I can tell, this is a behavior of the Windows API, so if that's
happening to you, you might want to do an HDR change followed by a resolution change.
15 changes: 15 additions & 0 deletions src/display_monitors.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from ctypes import byref, c_ulong, sizeof
from ctypes.wintypes import BOOL
from time import sleep
from typing import Optional

from custom_types import (
Expand All @@ -22,7 +24,9 @@
DisplayConfigGetDeviceInfo,
DisplayConfigSetDeviceInfo,
GetDisplayConfigBufferSizes,
InternalRefreshCalibration,
QueryDisplayConfig,
WcsGetCalibrationManagementState,
)


Expand Down Expand Up @@ -145,6 +149,17 @@ def set_hdr_state_for_monitor(enabled: bool, monitor: DisplayMonitor):
f"Failed to change HDR state with result {result}"
)

sleep(3)

is_calibration_management_enabled = BOOL()

if not WcsGetCalibrationManagementState(
byref(is_calibration_management_enabled)
):
raise DisplayMonitorException("Failed to get calibration management state")

InternalRefreshCalibration(0, 0)

except OSError as e:
raise DisplayMonitorException(f"Failed to change HDR state with error {e}")

Expand Down
108 changes: 58 additions & 50 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from termcolor._types import Attribute, Color

# Application metadata
VERSION: str = "v3.0.1"
VERSION: str = "v3.0.2"
NAME: str = "ResolutionSwitcher"


Expand Down Expand Up @@ -117,6 +117,32 @@ def print_error(error: str):
print_message("Error: " + error, "red", attrs=["bold"])


def change_resolution(monitor_identifier: str, width: int, height: int, refresh: int):
display_mode: DisplayMode = DisplayMode(width, height, refresh)
print_message(
f"Attempting to change {monitor_identifier} settings to {str(display_mode)}"
)
set_display_mode_for_device(display_mode, monitor_identifier)
print_success("Display settings changed successfully")


def change_hdr(monitor_identifier: str, hdr: str):
hdr_state = True if hdr.lower() == "true" else False

for monitor in all_monitors:
if monitor.adapter.identifier == monitor_identifier:
if not monitor.is_hdr_supported():
print_success(f"{monitor.adapter.identifier} does not support HDR")
print_success("Exiting...")
exit(-1)

print_message(
f'Attempting to {"enable" if hdr_state else "disable"} HDR on {monitor_identifier}'
)
set_hdr_state_for_monitor(hdr_state, monitor)
print_success(f"HDR {'enabled' if hdr_state else 'disabled'} successfully")


if __name__ == "__main__":
parser = argument_parser()
args = parser.parse_args()
Expand All @@ -127,63 +153,51 @@ def print_error(error: str):
print_error("No monitors found")
exit(-1)

if args.hdr is not None:
if args.hdr.lower() not in ["true", "false"]:
print_error("Valid values for HDR are 'true' or 'false'")
exit(-1)

try:
identifier: str = args.monitor

if identifier is None:
identifier = get_primary_monitor(all_monitors).identifier()

change_hdr(identifier, args.hdr)

exit(0)

except PrimaryMonitorException as e:
print_error(str(e))
exit(-1)

except HdrException as e:
print_error(
f"Error when trying to change HDR state. Failed with error {str(e)}"
)
exit(-1)

if args.width or args.height or args.refresh:
should_change_resolution: bool = (
args.width is not None
and args.height is not None
and args.refresh is not None
)
should_change_hdr: bool = args.hdr is not None

if not should_change_resolution:
print_error("Width, height and refresh rate are required")

if not should_change_hdr:
exit(-1)

if should_change_hdr:
if args.hdr.lower() not in ["true", "false"]:
print_error("Valid values for HDR are 'true' or 'false'")
exit(-1)
print_error("Width, height, and refresh rate are required for resolution change")
exit(-1)

try:
identifier: str = args.monitor

if identifier is None:
print_message("Monitor ID not specified")
print_message("Will attempt to identify primary monitor")
identifier = get_primary_monitor(all_monitors).identifier()

if should_change_resolution:
display_mode: DisplayMode = DisplayMode(
args.width, args.height, args.refresh
)

print_message(
f"Attempting to change {identifier} settings to {str(display_mode)}"
)
set_display_mode_for_device(display_mode, identifier)
print_success("Display settings changed successfully")

if should_change_hdr:
for target_monitor in all_monitors:
if target_monitor.adapter.identifier == identifier:
if not target_monitor.is_hdr_supported():
print_success(
f"{target_monitor.adapter.identifier} does not support HDR"
)
print_success("Exiting...")
exit(-1)

hdr_state = True if args.hdr.lower() == "true" else False

print_message(
f'Attempting to {"enable" if hdr_state else "disable"} HDR on {identifier}'
)
set_hdr_state_for_monitor(hdr_state, target_monitor)
print_success(
f"HDR {'enabled' if hdr_state else 'disabled'} successfully"
)
change_resolution(identifier, args.width, args.height, args.refresh)

exit(0)

except DisplayAdapterException as e:
print_error(str(e))
Expand All @@ -193,13 +207,7 @@ def print_error(error: str):
print_error(str(e))
exit(-1)

except HdrException as e:
print_error(
f"Error when trying to change HDR state. Failed with error {str(e)}"
)
exit(-1)

elif args.monitor is not None:
if args.monitor is not None:
identifier: str = args.monitor

for target_monitor in all_monitors:
Expand Down
11 changes: 11 additions & 0 deletions src/windows_types.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from ctypes import POINTER, Structure, Union, WinDLL, c_uint16, c_uint32, c_uint64
from ctypes.wintypes import (
BOOL,
DWORD,
HWND,
LONG,
Expand Down Expand Up @@ -392,6 +393,7 @@ class DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE(Structure):

# Imported API functions
user32DLL = WinDLL("user32")
mscmsDLL = WinDLL("mscms")

# https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-changedisplaysettingsexw
ChangeDisplaySettingsExW = user32DLL.ChangeDisplaySettingsExW
Expand Down Expand Up @@ -434,3 +436,12 @@ class DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE(Structure):
POINTER(DISPLAYCONFIG_MODE_INFO),
POINTER(c_uint32),
]

# https://learn.microsoft.com/en-us/windows/win32/api/icm/nf-icm-wcsgetcalibrationmanagementstate
WcsGetCalibrationManagementState = mscmsDLL.WcsGetCalibrationManagementState
WcsGetCalibrationManagementState.restype = BOOL
WcsGetCalibrationManagementState.argtypes = [POINTER(BOOL)]

InternalRefreshCalibration = mscmsDLL.InternalRefreshCalibration
InternalRefreshCalibration.restype = LONG
InternalRefreshCalibration.argtypes = [LONG, LONG]

0 comments on commit d9c75e8

Please sign in to comment.