Enhance Samsung monitor detection logic and add unit tests for trigger candidate selection
This commit is contained in:
+55
-1
@@ -4,6 +4,7 @@ from dataclasses import dataclass, field
|
|||||||
from typing import Protocol
|
from typing import Protocol
|
||||||
|
|
||||||
from monitorcontrol import get_monitors
|
from monitorcontrol import get_monitors
|
||||||
|
from app.config import SUPPORTED_TARGET_PORTS
|
||||||
|
|
||||||
|
|
||||||
ALIENWARE_MODEL_TOKEN = "AW3423DWF"
|
ALIENWARE_MODEL_TOKEN = "AW3423DWF"
|
||||||
@@ -86,7 +87,22 @@ class RealMonitorBackend:
|
|||||||
elif not trigger_candidates:
|
elif not trigger_candidates:
|
||||||
errors.append("Samsung trigger monitor could not be identified through DDC/CI.")
|
errors.append("Samsung trigger monitor could not be identified through DDC/CI.")
|
||||||
else:
|
else:
|
||||||
if samsung_named_candidates:
|
scanned_candidates: list[tuple[object, str, int | None]] = []
|
||||||
|
for monitor, description in trigger_candidates:
|
||||||
|
local_errors: list[str] = []
|
||||||
|
input_code = self._read_input_code(
|
||||||
|
monitor,
|
||||||
|
"Samsung trigger candidate monitor",
|
||||||
|
local_errors,
|
||||||
|
)
|
||||||
|
scanned_candidates.append((monitor, description, input_code))
|
||||||
|
|
||||||
|
selected_index = _select_trigger_candidate_index(
|
||||||
|
[(description, input_code) for _, description, input_code in scanned_candidates]
|
||||||
|
)
|
||||||
|
if selected_index is not None:
|
||||||
|
_, trigger_description, trigger_input_code = scanned_candidates[selected_index]
|
||||||
|
elif samsung_named_candidates:
|
||||||
errors.append("Multiple Samsung DDC monitors were detected; trigger monitor is ambiguous.")
|
errors.append("Multiple Samsung DDC monitors were detected; trigger monitor is ambiguous.")
|
||||||
else:
|
else:
|
||||||
errors.append(
|
errors.append(
|
||||||
@@ -172,3 +188,41 @@ def _is_samsung_description(description: str) -> bool:
|
|||||||
if normalized.startswith("SAM"):
|
if normalized.startswith("SAM"):
|
||||||
return True
|
return True
|
||||||
return " SAM " in f" {normalized} "
|
return " SAM " in f" {normalized} "
|
||||||
|
|
||||||
|
|
||||||
|
def _is_generic_description(description: str) -> bool:
|
||||||
|
normalized = description.upper().strip()
|
||||||
|
return "GENERIC" in normalized or "UNKNOWN" in normalized
|
||||||
|
|
||||||
|
|
||||||
|
def _trigger_input_codes() -> set[int]:
|
||||||
|
codes: set[int] = set()
|
||||||
|
for port_spec in SUPPORTED_TARGET_PORTS.values():
|
||||||
|
input_codes = port_spec.get("input_codes", set())
|
||||||
|
codes.update(int(code) for code in input_codes)
|
||||||
|
return codes
|
||||||
|
|
||||||
|
|
||||||
|
def _select_trigger_candidate_index(candidates: list[tuple[str, int | None]]) -> int | None:
|
||||||
|
if not candidates:
|
||||||
|
return None
|
||||||
|
|
||||||
|
trigger_codes = _trigger_input_codes()
|
||||||
|
matching = [
|
||||||
|
index
|
||||||
|
for index, (_, input_code) in enumerate(candidates)
|
||||||
|
if input_code is not None and int(input_code) in trigger_codes
|
||||||
|
]
|
||||||
|
if len(matching) == 1:
|
||||||
|
return matching[0]
|
||||||
|
|
||||||
|
if len(matching) > 1:
|
||||||
|
generic_matches = [index for index in matching if _is_generic_description(candidates[index][0])]
|
||||||
|
if len(generic_matches) == 1:
|
||||||
|
return generic_matches[0]
|
||||||
|
|
||||||
|
generic = [index for index, (description, _) in enumerate(candidates) if _is_generic_description(description)]
|
||||||
|
if len(generic) == 1:
|
||||||
|
return generic[0]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|||||||
+4
-25
@@ -1,28 +1,7 @@
|
|||||||
from monitorcontrol import get_monitors
|
from monitorcontrol import get_monitors, InputSource
|
||||||
|
|
||||||
def get_monitor_info():
|
|
||||||
results = []
|
|
||||||
for monitor in get_monitors():
|
for monitor in get_monitors():
|
||||||
# Set a breakpoint here to see each monitor object as it's found
|
|
||||||
m_data = {"name": "Unknown/Internal", "input": "N/A", "caps": ""}
|
|
||||||
try:
|
|
||||||
with monitor:
|
with monitor:
|
||||||
# This is the line that was crashing; now it's protected
|
print(monitor.vcp.description)
|
||||||
m_data["caps"] = monitor.get_vcp_capabilities()
|
input_source_raw: int = monitor.get_input_source()
|
||||||
m_data["input"] = str(monitor.get_input_source())
|
print(InputSource(input_source_raw).name)
|
||||||
|
|
||||||
if "AW34" in m_data["caps"]:
|
|
||||||
m_data["name"] = "ALIENWARE (Target)"
|
|
||||||
elif "SAM" in m_data["caps"]:
|
|
||||||
m_data["name"] = "SAMSUNG (Trigger)"
|
|
||||||
except Exception:
|
|
||||||
# If a monitor (like the laptop screen) fails, we just skip it
|
|
||||||
continue
|
|
||||||
|
|
||||||
results.append(m_data)
|
|
||||||
return results
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print("Searching for DDC/CI compatible monitors...")
|
|
||||||
for m in get_monitor_info():
|
|
||||||
print(f"Detected: {m['name']} | Current Input: {m['input']}")
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from app.hardware import _select_trigger_candidate_index
|
||||||
|
|
||||||
|
|
||||||
|
def test_select_trigger_candidate_by_unique_trigger_code() -> None:
|
||||||
|
candidates = [
|
||||||
|
("DELL U2720Q", 60),
|
||||||
|
("Generic PnP Monitor", 19),
|
||||||
|
]
|
||||||
|
assert _select_trigger_candidate_index(candidates) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_select_trigger_candidate_by_generic_tiebreaker() -> None:
|
||||||
|
candidates = [
|
||||||
|
("DELL U2720Q", 15),
|
||||||
|
("Generic PnP Monitor", 19),
|
||||||
|
]
|
||||||
|
assert _select_trigger_candidate_index(candidates) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_select_trigger_candidate_none_when_still_ambiguous() -> None:
|
||||||
|
candidates = [
|
||||||
|
("Generic PnP Monitor #1", 15),
|
||||||
|
("Generic PnP Monitor #2", 19),
|
||||||
|
]
|
||||||
|
assert _select_trigger_candidate_index(candidates) is None
|
||||||
Reference in New Issue
Block a user