diff --git a/app/hardware.py b/app/hardware.py index d98f609..12e51bc 100644 --- a/app/hardware.py +++ b/app/hardware.py @@ -4,6 +4,7 @@ from dataclasses import dataclass, field from typing import Protocol from monitorcontrol import get_monitors +from app.config import SUPPORTED_TARGET_PORTS ALIENWARE_MODEL_TOKEN = "AW3423DWF" @@ -86,7 +87,22 @@ class RealMonitorBackend: elif not trigger_candidates: errors.append("Samsung trigger monitor could not be identified through DDC/CI.") 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.") else: errors.append( @@ -172,3 +188,41 @@ def _is_samsung_description(description: str) -> bool: if normalized.startswith("SAM"): return True 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 diff --git a/monitorcontrol_main.py b/monitorcontrol_main.py index eced497..4d3c431 100644 --- a/monitorcontrol_main.py +++ b/monitorcontrol_main.py @@ -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(): - # 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: - # This is the line that was crashing; now it's protected - m_data["caps"] = monitor.get_vcp_capabilities() - m_data["input"] = str(monitor.get_input_source()) - - 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']}") \ No newline at end of file +for monitor in get_monitors(): + with monitor: + print(monitor.vcp.description) + input_source_raw: int = monitor.get_input_source() + print(InputSource(input_source_raw).name) \ No newline at end of file diff --git a/tests/test_hardware.py b/tests/test_hardware.py new file mode 100644 index 0000000..008b9e2 --- /dev/null +++ b/tests/test_hardware.py @@ -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