Add display refresh rate detection and display
Some checks failed
Validate / validate (push) Failing after 8s

- Added get_monitor_refresh_rates() function in monitor_names.py using Windows ctypes/DEVMODE to detect monitor refresh rates
- Updated DisplayInfo dataclass and Pydantic schema to include refresh_rate field (in Hz)
- Modified get_available_displays() to detect and include refresh rates (defaults to 60Hz on non-Windows or if detection fails)
- Added refresh rate display in Web UI between Resolution and Position
- Added translations for refresh rate label (displays.refresh_rate) in English and Russian locales
- Cross-platform compatible: gracefully falls back to 60Hz default on non-Windows systems

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-06 17:18:35 +03:00
parent c40c8b9d26
commit c1259a9a7f
7 changed files with 105 additions and 3 deletions

View File

@@ -1,6 +1,6 @@
"""Utility functions and helpers."""
from .logger import setup_logging, get_logger
from .monitor_names import get_monitor_names, get_monitor_name
from .monitor_names import get_monitor_names, get_monitor_name, get_monitor_refresh_rates
__all__ = ["setup_logging", "get_logger", "get_monitor_names", "get_monitor_name"]
__all__ = ["setup_logging", "get_logger", "get_monitor_names", "get_monitor_name", "get_monitor_refresh_rates"]

View File

@@ -77,3 +77,90 @@ def get_monitor_name(index: int) -> str:
"""
monitor_names = get_monitor_names()
return monitor_names.get(index, f"Display {index}")
def get_monitor_refresh_rates() -> Dict[int, int]:
"""Get refresh rates (in Hz) for connected monitors.
On Windows, attempts to retrieve refresh rates from display settings.
On other platforms, returns empty dict (will use default 60Hz).
Returns:
Dictionary mapping display indices to refresh rates in Hz
"""
if sys.platform != "win32":
logger.debug("Refresh rate detection only supported on Windows")
return {}
try:
import ctypes
from ctypes import wintypes
# Define Windows structures
class DEVMODE(ctypes.Structure):
_fields_ = [
('dmDeviceName', ctypes.c_wchar * 32),
('dmSpecVersion', wintypes.WORD),
('dmDriverVersion', wintypes.WORD),
('dmSize', wintypes.WORD),
('dmDriverExtra', wintypes.WORD),
('dmFields', wintypes.DWORD),
('dmPositionX', wintypes.LONG),
('dmPositionY', wintypes.LONG),
('dmDisplayOrientation', wintypes.DWORD),
('dmDisplayFixedOutput', wintypes.DWORD),
('dmColor', wintypes.SHORT),
('dmDuplex', wintypes.SHORT),
('dmYResolution', wintypes.SHORT),
('dmTTOption', wintypes.SHORT),
('dmCollate', wintypes.SHORT),
('dmFormName', ctypes.c_wchar * 32),
('dmLogPixels', wintypes.WORD),
('dmBitsPerPel', wintypes.DWORD),
('dmPelsWidth', wintypes.DWORD),
('dmPelsHeight', wintypes.DWORD),
('dmDisplayFlags', wintypes.DWORD),
('dmDisplayFrequency', wintypes.DWORD),
]
user32 = ctypes.windll.user32
refresh_rates = {}
# Enumerate all display devices
idx = 0
while True:
device_name = ctypes.create_unicode_buffer(32)
if not user32.EnumDisplayDevicesW(None, idx, None, 0):
# Try getting display settings by index
devmode = DEVMODE()
devmode.dmSize = ctypes.sizeof(DEVMODE)
if user32.EnumDisplaySettingsW(None, -1, ctypes.byref(devmode)): # ENUM_CURRENT_SETTINGS = -1
refresh_rate = devmode.dmDisplayFrequency
if refresh_rate > 0:
refresh_rates[idx] = refresh_rate
logger.debug(f"Display {idx}: {refresh_rate}Hz")
idx += 1
if idx > 10: # Safety limit
break
else:
break
# If no refresh rates found, try alternative method
if not refresh_rates:
devmode = DEVMODE()
devmode.dmSize = ctypes.sizeof(DEVMODE)
for monitor_idx in range(4): # Check up to 4 monitors
if user32.EnumDisplaySettingsW(None, -1, ctypes.byref(devmode)):
refresh_rate = devmode.dmDisplayFrequency
if refresh_rate > 0:
refresh_rates[monitor_idx] = refresh_rate
logger.debug(f"Monitor {monitor_idx}: {refresh_rate}Hz")
return refresh_rates
except Exception as e:
logger.debug(f"Failed to retrieve monitor refresh rates: {e}")
return {}