diff --git a/server/src/wled_controller/api/schemas.py b/server/src/wled_controller/api/schemas.py
index 7a58b66..bdf180f 100644
--- a/server/src/wled_controller/api/schemas.py
+++ b/server/src/wled_controller/api/schemas.py
@@ -36,6 +36,7 @@ class DisplayInfo(BaseModel):
x: int = Field(description="Display X position")
y: int = Field(description="Display Y position")
is_primary: bool = Field(default=False, description="Whether this is the primary display")
+ refresh_rate: int = Field(description="Display refresh rate in Hz")
class DisplayListResponse(BaseModel):
diff --git a/server/src/wled_controller/core/screen_capture.py b/server/src/wled_controller/core/screen_capture.py
index 66af6f6..ef8fd40 100644
--- a/server/src/wled_controller/core/screen_capture.py
+++ b/server/src/wled_controller/core/screen_capture.py
@@ -7,7 +7,7 @@ import mss
import numpy as np
from PIL import Image
-from wled_controller.utils import get_logger, get_monitor_names
+from wled_controller.utils import get_logger, get_monitor_names, get_monitor_refresh_rates
logger = get_logger(__name__)
@@ -23,6 +23,7 @@ class DisplayInfo:
x: int
y: int
is_primary: bool
+ refresh_rate: int # in Hz
@dataclass
@@ -58,6 +59,9 @@ def get_available_displays() -> List[DisplayInfo]:
# Get friendly monitor names (Windows only, falls back to generic names)
monitor_names = get_monitor_names()
+ # Get monitor refresh rates (Windows only, falls back to 60Hz)
+ refresh_rates = get_monitor_refresh_rates()
+
with mss.mss() as sct:
displays = []
@@ -66,6 +70,9 @@ def get_available_displays() -> List[DisplayInfo]:
# Use friendly name from WMI if available, otherwise generic name
friendly_name = monitor_names.get(idx, f"Display {idx}")
+ # Use detected refresh rate or default to 60Hz
+ refresh_rate = refresh_rates.get(idx, 60)
+
display_info = DisplayInfo(
index=idx,
name=friendly_name,
@@ -74,6 +81,7 @@ def get_available_displays() -> List[DisplayInfo]:
x=monitor["left"],
y=monitor["top"],
is_primary=(idx == 0),
+ refresh_rate=refresh_rate,
)
displays.append(display_info)
diff --git a/server/src/wled_controller/static/app.js b/server/src/wled_controller/static/app.js
index e8b3583..beb29f9 100644
--- a/server/src/wled_controller/static/app.js
+++ b/server/src/wled_controller/static/app.js
@@ -268,6 +268,10 @@ async function loadDisplays() {
${t('displays.resolution')}
${display.width} × ${display.height}
+
+ ${t('displays.refresh_rate')}
+ ${display.refresh_rate}Hz
+
${t('displays.position')}
(${display.x}, ${display.y})
diff --git a/server/src/wled_controller/static/locales/en.json b/server/src/wled_controller/static/locales/en.json
index 8d0f507..7a84444 100644
--- a/server/src/wled_controller/static/locales/en.json
+++ b/server/src/wled_controller/static/locales/en.json
@@ -26,6 +26,7 @@
"displays.badge.primary": "Primary",
"displays.badge.secondary": "Secondary",
"displays.resolution": "Resolution:",
+ "displays.refresh_rate": "Refresh Rate:",
"displays.position": "Position:",
"displays.index": "Display Index:",
"displays.loading": "Loading displays...",
diff --git a/server/src/wled_controller/static/locales/ru.json b/server/src/wled_controller/static/locales/ru.json
index 84fb8c0..ce56cdb 100644
--- a/server/src/wled_controller/static/locales/ru.json
+++ b/server/src/wled_controller/static/locales/ru.json
@@ -26,6 +26,7 @@
"displays.badge.primary": "Основной",
"displays.badge.secondary": "Вторичный",
"displays.resolution": "Разрешение:",
+ "displays.refresh_rate": "Частота Обновления:",
"displays.position": "Позиция:",
"displays.index": "Индекс Дисплея:",
"displays.loading": "Загрузка дисплеев...",
diff --git a/server/src/wled_controller/utils/__init__.py b/server/src/wled_controller/utils/__init__.py
index 4e58a9b..6e2fec9 100644
--- a/server/src/wled_controller/utils/__init__.py
+++ b/server/src/wled_controller/utils/__init__.py
@@ -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"]
diff --git a/server/src/wled_controller/utils/monitor_names.py b/server/src/wled_controller/utils/monitor_names.py
index b8039f6..810c374 100644
--- a/server/src/wled_controller/utils/monitor_names.py
+++ b/server/src/wled_controller/utils/monitor_names.py
@@ -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 {}