Reset switch session on trigger changes and add DP2 force helper

This commit is contained in:
Lago
2026-03-27 15:53:16 +01:00
parent a4837c1172
commit 3f7c5a0677
3 changed files with 129 additions and 19 deletions
+25 -14
View File
@@ -80,6 +80,7 @@ class KvmSwitcherService:
self._samsung_session_attempted = False self._samsung_session_attempted = False
self._samsung_session_successful = False self._samsung_session_successful = False
self._samsung_session_attempt_count = 0 self._samsung_session_attempt_count = 0
self._last_trigger_input_code: int | None = None
self._state_lock = Lock() self._state_lock = Lock()
self._stop_event = Event() self._stop_event = Event()
self._thread: Thread | None = None self._thread: Thread | None = None
@@ -115,7 +116,6 @@ class KvmSwitcherService:
def poll_once(self) -> dict[str, Any]: def poll_once(self) -> dict[str, Any]:
config = self.config_store.get() config = self.config_store.get()
scan = self.monitor_backend.scan() scan = self.monitor_backend.scan()
self._update_samsung_session(scan.samsung_present)
errors = list(scan.errors) errors = list(scan.errors)
errors.extend(config.validate()) errors.extend(config.validate())
@@ -124,6 +124,7 @@ class KvmSwitcherService:
scan=scan, scan=scan,
errors=errors, errors=errors,
) )
self._update_samsung_session(scan.samsung_present, trigger_input_code)
ddm_slot = self.ddm_backend.resolve_alienware_slot(force=False) ddm_slot = self.ddm_backend.resolve_alienware_slot(force=False)
supports_monitor_targeting = bool( supports_monitor_targeting = bool(
@@ -242,18 +243,28 @@ class KvmSwitcherService:
return port_name return port_name
return None return None
def _update_samsung_session(self, samsung_present: bool) -> None: def _update_samsung_session(self, samsung_present: bool, trigger_input_code: int | None) -> None:
if samsung_present: if not samsung_present:
if not self._samsung_session_active:
self._samsung_session_active = True
self._samsung_session_attempted = False
self._samsung_session_successful = False
self._samsung_session_attempt_count = 0
else:
self._samsung_session_active = False self._samsung_session_active = False
self._samsung_session_attempted = False self._samsung_session_attempted = False
self._samsung_session_successful = False self._samsung_session_successful = False
self._samsung_session_attempt_count = 0 self._samsung_session_attempt_count = 0
self._last_trigger_input_code = None
return
if not self._samsung_session_active:
self._samsung_session_active = True
self._samsung_session_attempted = False
self._samsung_session_successful = False
self._samsung_session_attempt_count = 0
self._last_trigger_input_code = trigger_input_code
return
if trigger_input_code != self._last_trigger_input_code:
self._samsung_session_attempted = False
self._samsung_session_successful = False
self._samsung_session_attempt_count = 0
self._last_trigger_input_code = trigger_input_code
def _attempt_switch_sequence( def _attempt_switch_sequence(
self, self,
@@ -284,16 +295,16 @@ class KvmSwitcherService:
verify_scan = self.monitor_backend.scan() verify_scan = self.monitor_backend.scan()
scan = verify_scan scan = verify_scan
errors.extend(verify_scan.errors) errors.extend(verify_scan.errors)
self._update_samsung_session(verify_scan.samsung_present)
if not verify_scan.samsung_present:
return "waiting_for_reconnect", last_switch_at, scan, errors
verify_trigger_input, _, _ = self._resolve_trigger_state( verify_trigger_input, _, _ = self._resolve_trigger_state(
config=config, config=config,
scan=verify_scan, scan=verify_scan,
errors=errors, errors=errors,
) )
self._update_samsung_session(verify_scan.samsung_present, verify_trigger_input)
if not verify_scan.samsung_present:
return "waiting_for_reconnect", last_switch_at, scan, errors
desired_codes = self._port_input_codes(desired_port) desired_codes = self._port_input_codes(desired_port)
if verify_trigger_input not in desired_codes: if verify_trigger_input not in desired_codes:
return "waiting_for_trigger_match", last_switch_at, scan, errors return "waiting_for_trigger_match", last_switch_at, scan, errors
+20 -5
View File
@@ -1,7 +1,22 @@
from monitorcontrol import get_monitors, InputSource from monitorcontrol import get_monitors
TARGET_MODEL = "AW3423DWF"
DP2_CODE = 19
for monitor in get_monitors(): for monitor in get_monitors():
with monitor: description = str(getattr(getattr(monitor, "vcp", None), "description", "") or str(monitor))
print(monitor.vcp.description) if TARGET_MODEL not in description.upper():
input_source_raw: int = monitor.get_input_source() continue
print(InputSource(input_source_raw).name)
try:
with monitor:
monitor.set_input_source(DP2_CODE)
print(f"Target: {description}")
print(f"Forced DP2 code {DP2_CODE}.")
except Exception as exc:
print(f"Failed forcing DP2 on {description}: {exc}")
break
else:
print(f"Monitor {TARGET_MODEL} not found.")
+84
View File
@@ -441,3 +441,87 @@ def test_polling_switches_when_alienware_input_is_unreadable() -> None:
assert status["last_switch_result"] == "switched_unverified" assert status["last_switch_result"] == "switched_unverified"
assert status["samsung_session_successful"] is True assert status["samsung_session_successful"] is True
assert backend.calls == [(1, "DP1")] assert backend.calls == [(1, "DP1")]
def test_polling_resets_session_when_trigger_changes_without_disconnect() -> None:
service_module = _require_module("app.service")
hardware_module = _require_module("app.hardware")
ddm_module = _require_module("app.ddm")
KvmSwitcherService = getattr(service_module, "KvmSwitcherService", None)
HardwareScan = getattr(hardware_module, "HardwareScan", None)
DDMCommandResult = getattr(ddm_module, "DDMCommandResult", None)
if KvmSwitcherService is None or HardwareScan is None or DDMCommandResult is None:
pytest.skip("Service backend interfaces are not available yet.")
class FakeMonitorBackend:
def __init__(self) -> None:
self.call_count = 0
def scan(self):
self.call_count += 1
if self.call_count == 1:
return HardwareScan(
samsung_present=True,
trigger_input_code=19,
alienware_detected=True,
alienware_input_code=19,
errors=[],
)
if self.call_count == 2:
return HardwareScan(
samsung_present=True,
trigger_input_code=15,
alienware_detected=True,
alienware_input_code=15,
errors=[],
)
if self.call_count == 3:
return HardwareScan(
samsung_present=True,
trigger_input_code=19,
alienware_detected=True,
alienware_input_code=15,
errors=[],
)
return HardwareScan(
samsung_present=True,
trigger_input_code=19,
alienware_detected=True,
alienware_input_code=19,
errors=[],
)
class FakeDDMBackend:
def __init__(self) -> None:
self.calls: list[tuple[int, str]] = []
def is_available(self) -> bool:
return True
def resolve_alienware_slot(self, force: bool = False) -> int | None:
return 1
def invalidate_slot(self) -> None:
return None
def switch_to_port(self, slot: int, port_name: str):
self.calls.append((slot, port_name))
return DDMCommandResult(True, "ok")
config_path = Path(tempfile.mkdtemp()) / "config.json"
backend = FakeDDMBackend()
service = KvmSwitcherService(
config_store=ConfigStore(config_path),
monitor_backend=FakeMonitorBackend(),
ddm_backend=backend,
poll_interval_seconds=0.01,
retry_wait_seconds=0.0,
)
first = service.save_settings(device_port="DP2")
assert first["last_switch_result"] == "noop"
second = service.poll_once()
assert second["last_switch_result"] == "waiting_for_trigger_match"
third = service.poll_once()
assert third["last_switch_result"] == "switched"
assert backend.calls == [(1, "DP2")]