feat: full implementation - switcher, device watcher, hotkeys, tray, settings UI

This commit is contained in:
Miłosz Matysiak
2026-04-09 10:09:01 +02:00
parent 40d1c23051
commit 83293c8a91
13 changed files with 1162 additions and 0 deletions

100
config/config_manager.py Normal file
View File

@@ -0,0 +1,100 @@
import json
import copy
from pathlib import Path
from typing import Callable
DEFAULT_CONFIG = {
"active_profile": "pc1_dp",
"profiles": [
{
"id": "pc1_dp",
"name": "PC1 \u2014 DP",
"hotkey": "ctrl+alt+1",
"monitor_inputs": [
{"monitor_index": 0, "vcp_value": 15},
{"monitor_index": 1, "vcp_value": 15}
]
},
{
"id": "pc2_hdmi",
"name": "PC2 \u2014 HDMI",
"hotkey": "ctrl+alt+2",
"monitor_inputs": [
{"monitor_index": 0, "vcp_value": 17},
{"monitor_index": 1, "vcp_value": 17}
]
}
],
"device_triggers": [
{
"name": "HP 975 keyboard",
"bt_device_id": "VID&0203F0_PID&6343",
"on_connect": "pc1_dp",
"on_disconnect": "pc2_hdmi",
"enabled": True
},
{
"name": "MX Anywhere 2S",
"bt_device_id": "VID&02046D_PID&B01A",
"on_connect": "pc1_dp",
"on_disconnect": "pc2_hdmi",
"enabled": True
}
],
"general": {
"autostart": True,
"minimize_to_tray": True,
"debounce_ms": 500
}
}
class ConfigManager:
def __init__(self, config_path=None):
if config_path is None:
config_path = Path(__file__).parent / "config.json"
self.config_path = Path(config_path)
self._config: dict = {}
self._callbacks: list[Callable] = []
self.load()
def load(self) -> None:
if self.config_path.exists():
try:
with open(self.config_path, "r", encoding="utf-8") as f:
self._config = json.load(f)
except (json.JSONDecodeError, OSError):
self._config = copy.deepcopy(DEFAULT_CONFIG)
else:
self._config = copy.deepcopy(DEFAULT_CONFIG)
self.save()
def save(self) -> None:
self.config_path.parent.mkdir(parents=True, exist_ok=True)
with open(self.config_path, "w", encoding="utf-8") as f:
json.dump(self._config, f, indent=2, ensure_ascii=False)
def get(self) -> dict:
return self._config
def update(self, new_config: dict) -> None:
self._config = new_config
self.save()
for cb in self._callbacks:
cb(self._config)
def on_change(self, callback: Callable) -> None:
self._callbacks.append(callback)
def get_profile(self, profile_id: str) -> dict | None:
for p in self._config.get("profiles", []):
if p["id"] == profile_id:
return p
return None
def get_active_profile_id(self) -> str:
return self._config.get("active_profile", "pc1_dp")
def set_active_profile(self, profile_id: str) -> None:
self._config["active_profile"] = profile_id
self.save()