343 lines
13 KiB
Python
343 lines
13 KiB
Python
import copy
|
|
import logging
|
|
import sys
|
|
import winreg
|
|
|
|
from PyQt6.QtWidgets import (
|
|
QMainWindow, QWidget, QTabWidget, QVBoxLayout, QHBoxLayout,
|
|
QPushButton, QListWidget, QListWidgetItem, QLabel, QLineEdit,
|
|
QFormLayout, QSpinBox, QCheckBox, QComboBox, QGroupBox,
|
|
)
|
|
from PyQt6.QtCore import Qt
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
VCP_INPUT_OPTIONS = [
|
|
(15, "DisplayPort-1 (0x0F)"),
|
|
(16, "DisplayPort-2 (0x10)"),
|
|
(17, "HDMI-1 (0x11)"),
|
|
(18, "HDMI-2 (0x12)"),
|
|
(1, "VGA-1 (0x01)"),
|
|
(3, "DVI-1 (0x03)"),
|
|
]
|
|
|
|
|
|
class SettingsWindow(QMainWindow):
|
|
def __init__(self, config_manager, switcher, hotkey_manager):
|
|
super().__init__()
|
|
self._config_manager = config_manager
|
|
self._switcher = switcher
|
|
self._hotkey_manager = hotkey_manager
|
|
self._working_config = copy.deepcopy(config_manager.get())
|
|
|
|
self.setWindowTitle("MonitorSwitcher \u2014 Ustawienia")
|
|
self.setMinimumSize(640, 520)
|
|
|
|
tabs = QTabWidget()
|
|
tabs.addTab(self._build_profiles_tab(), "Profile")
|
|
tabs.addTab(self._build_devices_tab(), "Urzadzenia BT")
|
|
tabs.addTab(self._build_hotkeys_tab(), "Hotkeys")
|
|
tabs.addTab(self._build_general_tab(), "Ogolne")
|
|
|
|
save_btn = QPushButton("Zapisz i zamknij")
|
|
save_btn.clicked.connect(self._save_and_close)
|
|
cancel_btn = QPushButton("Anuluj")
|
|
cancel_btn.clicked.connect(self.close)
|
|
|
|
btn_row = QHBoxLayout()
|
|
btn_row.addStretch()
|
|
btn_row.addWidget(cancel_btn)
|
|
btn_row.addWidget(save_btn)
|
|
|
|
layout = QVBoxLayout()
|
|
layout.addWidget(tabs)
|
|
layout.addLayout(btn_row)
|
|
|
|
central = QWidget()
|
|
central.setLayout(layout)
|
|
self.setCentralWidget(central)
|
|
|
|
# ── Profiles tab ──────────────────────────────────────────────────────────
|
|
|
|
def _build_profiles_tab(self) -> QWidget:
|
|
widget = QWidget()
|
|
layout = QHBoxLayout(widget)
|
|
|
|
left = QVBoxLayout()
|
|
self._profile_list = QListWidget()
|
|
self._profile_list.currentRowChanged.connect(self._on_profile_selected)
|
|
left.addWidget(QLabel("Profile:"))
|
|
left.addWidget(self._profile_list)
|
|
|
|
add_btn = QPushButton("+ Dodaj")
|
|
add_btn.clicked.connect(self._add_profile)
|
|
del_btn = QPushButton("Usun")
|
|
del_btn.clicked.connect(self._delete_profile)
|
|
btn_row = QHBoxLayout()
|
|
btn_row.addWidget(add_btn)
|
|
btn_row.addWidget(del_btn)
|
|
left.addLayout(btn_row)
|
|
|
|
right = QVBoxLayout()
|
|
self._profile_name_edit = QLineEdit()
|
|
self._profile_name_edit.setPlaceholderText("Nazwa profilu")
|
|
self._profile_name_edit.textChanged.connect(self._on_profile_name_changed)
|
|
right.addWidget(QLabel("Nazwa:"))
|
|
right.addWidget(self._profile_name_edit)
|
|
|
|
self._monitor_combos: list[QComboBox] = []
|
|
monitor_group = QGroupBox("Wejscia monitorow")
|
|
monitor_layout = QFormLayout()
|
|
for i in range(2):
|
|
combo = QComboBox()
|
|
for vcp, label in VCP_INPUT_OPTIONS:
|
|
combo.addItem(label, vcp)
|
|
combo.currentIndexChanged.connect(
|
|
lambda _, idx=i: self._on_monitor_input_changed(idx)
|
|
)
|
|
self._monitor_combos.append(combo)
|
|
monitor_layout.addRow(f"Monitor {i}:", combo)
|
|
monitor_group.setLayout(monitor_layout)
|
|
right.addWidget(monitor_group)
|
|
right.addStretch()
|
|
|
|
layout.addLayout(left, 1)
|
|
layout.addLayout(right, 2)
|
|
|
|
self._refresh_profile_list()
|
|
return widget
|
|
|
|
def _refresh_profile_list(self) -> None:
|
|
self._profile_list.clear()
|
|
for p in self._working_config.get("profiles", []):
|
|
self._profile_list.addItem(QListWidgetItem(p["name"]))
|
|
if self._profile_list.count() > 0:
|
|
self._profile_list.setCurrentRow(0)
|
|
|
|
def _on_profile_selected(self, row: int) -> None:
|
|
profiles = self._working_config.get("profiles", [])
|
|
if row < 0 or row >= len(profiles):
|
|
return
|
|
profile = profiles[row]
|
|
self._profile_name_edit.blockSignals(True)
|
|
self._profile_name_edit.setText(profile["name"])
|
|
self._profile_name_edit.blockSignals(False)
|
|
for combo in self._monitor_combos:
|
|
combo.blockSignals(True)
|
|
for inp in profile.get("monitor_inputs", []):
|
|
idx = inp["monitor_index"]
|
|
vcp = inp["vcp_value"]
|
|
if idx < len(self._monitor_combos):
|
|
data_idx = self._monitor_combos[idx].findData(vcp)
|
|
self._monitor_combos[idx].setCurrentIndex(
|
|
data_idx if data_idx >= 0 else 0
|
|
)
|
|
for combo in self._monitor_combos:
|
|
combo.blockSignals(False)
|
|
|
|
def _on_profile_name_changed(self, text: str) -> None:
|
|
row = self._profile_list.currentRow()
|
|
profiles = self._working_config.get("profiles", [])
|
|
if 0 <= row < len(profiles):
|
|
profiles[row]["name"] = text
|
|
self._profile_list.item(row).setText(text)
|
|
|
|
def _on_monitor_input_changed(self, monitor_idx: int) -> None:
|
|
row = self._profile_list.currentRow()
|
|
profiles = self._working_config.get("profiles", [])
|
|
if row < 0 or row >= len(profiles):
|
|
return
|
|
vcp = self._monitor_combos[monitor_idx].currentData()
|
|
inputs = profiles[row].setdefault("monitor_inputs", [])
|
|
for inp in inputs:
|
|
if inp["monitor_index"] == monitor_idx:
|
|
inp["vcp_value"] = vcp
|
|
return
|
|
inputs.append({"monitor_index": monitor_idx, "vcp_value": vcp})
|
|
|
|
def _add_profile(self) -> None:
|
|
new_id = f"profile_{len(self._working_config['profiles'])}"
|
|
new_profile = {
|
|
"id": new_id,
|
|
"name": "Nowy profil",
|
|
"hotkey": "",
|
|
"monitor_inputs": [
|
|
{"monitor_index": 0, "vcp_value": 15},
|
|
{"monitor_index": 1, "vcp_value": 15},
|
|
],
|
|
}
|
|
self._working_config["profiles"].append(new_profile)
|
|
self._profile_list.addItem(QListWidgetItem(new_profile["name"]))
|
|
self._profile_list.setCurrentRow(self._profile_list.count() - 1)
|
|
|
|
def _delete_profile(self) -> None:
|
|
row = self._profile_list.currentRow()
|
|
if row < 0:
|
|
return
|
|
self._working_config["profiles"].pop(row)
|
|
self._refresh_profile_list()
|
|
|
|
# ── Devices tab ───────────────────────────────────────────────────────────
|
|
|
|
def _build_devices_tab(self) -> QWidget:
|
|
widget = QWidget()
|
|
outer = QVBoxLayout(widget)
|
|
outer.addWidget(QLabel("Urzadzenia BT wyzwalajace przelaczeanie profilu:"))
|
|
|
|
self._devices_container = QVBoxLayout()
|
|
outer.addLayout(self._devices_container)
|
|
|
|
add_btn = QPushButton("+ Dodaj urzadzenie")
|
|
add_btn.clicked.connect(lambda: self._add_device_row())
|
|
outer.addWidget(add_btn)
|
|
outer.addStretch()
|
|
|
|
for trigger in self._working_config.get("device_triggers", []):
|
|
self._add_device_row(trigger)
|
|
|
|
return widget
|
|
|
|
def _add_device_row(self, trigger: dict | None = None) -> None:
|
|
if trigger is None:
|
|
trigger = {
|
|
"name": "Nowe urzadzenie",
|
|
"bt_device_id": "",
|
|
"on_connect": self._first_profile_id(),
|
|
"on_disconnect": self._first_profile_id(),
|
|
"enabled": True,
|
|
}
|
|
self._working_config.setdefault("device_triggers", []).append(trigger)
|
|
|
|
group = QGroupBox(trigger.get("name", ""))
|
|
form = QFormLayout(group)
|
|
|
|
name_edit = QLineEdit(trigger.get("name", ""))
|
|
bt_id_edit = QLineEdit(trigger.get("bt_device_id", ""))
|
|
connect_combo = self._profile_combo(trigger.get("on_connect", ""))
|
|
disconnect_combo = self._profile_combo(trigger.get("on_disconnect", ""))
|
|
enabled_cb = QCheckBox()
|
|
enabled_cb.setChecked(trigger.get("enabled", True))
|
|
|
|
form.addRow("Nazwa:", name_edit)
|
|
form.addRow("BT Device ID (fragment):", bt_id_edit)
|
|
form.addRow("Przy podlaczeniu:", connect_combo)
|
|
form.addRow("Przy odlaczeniu:", disconnect_combo)
|
|
form.addRow("Aktywne:", enabled_cb)
|
|
|
|
def update_trigger():
|
|
trigger["name"] = name_edit.text()
|
|
trigger["bt_device_id"] = bt_id_edit.text()
|
|
trigger["on_connect"] = connect_combo.currentData()
|
|
trigger["on_disconnect"] = disconnect_combo.currentData()
|
|
trigger["enabled"] = enabled_cb.isChecked()
|
|
group.setTitle(trigger["name"])
|
|
|
|
name_edit.textChanged.connect(lambda _: update_trigger())
|
|
bt_id_edit.textChanged.connect(lambda _: update_trigger())
|
|
connect_combo.currentIndexChanged.connect(lambda _: update_trigger())
|
|
disconnect_combo.currentIndexChanged.connect(lambda _: update_trigger())
|
|
enabled_cb.stateChanged.connect(lambda _: update_trigger())
|
|
|
|
self._devices_container.addWidget(group)
|
|
|
|
def _profile_combo(self, selected_id: str) -> QComboBox:
|
|
combo = QComboBox()
|
|
for p in self._working_config.get("profiles", []):
|
|
combo.addItem(p["name"], p["id"])
|
|
idx = combo.findData(selected_id)
|
|
if idx >= 0:
|
|
combo.setCurrentIndex(idx)
|
|
return combo
|
|
|
|
def _first_profile_id(self) -> str:
|
|
profiles = self._working_config.get("profiles", [])
|
|
return profiles[0]["id"] if profiles else ""
|
|
|
|
# ── Hotkeys tab ───────────────────────────────────────────────────────────
|
|
|
|
def _build_hotkeys_tab(self) -> QWidget:
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
layout.addWidget(QLabel("Przypisz skroty klawiszowe do profili:"))
|
|
layout.addWidget(QLabel("Format: ctrl+alt+1, win+F1, itp."))
|
|
|
|
form = QFormLayout()
|
|
for profile in self._working_config.get("profiles", []):
|
|
edit = QLineEdit(profile.get("hotkey", ""))
|
|
edit.setPlaceholderText("np. ctrl+alt+1")
|
|
pid = profile["id"]
|
|
edit.textChanged.connect(
|
|
lambda text, p=pid: self._on_hotkey_changed(p, text)
|
|
)
|
|
form.addRow(f"{profile['name']}:", edit)
|
|
|
|
layout.addLayout(form)
|
|
layout.addStretch()
|
|
return widget
|
|
|
|
def _on_hotkey_changed(self, profile_id: str, text: str) -> None:
|
|
for p in self._working_config.get("profiles", []):
|
|
if p["id"] == profile_id:
|
|
p["hotkey"] = text.strip()
|
|
return
|
|
|
|
# ── General tab ───────────────────────────────────────────────────────────
|
|
|
|
def _build_general_tab(self) -> QWidget:
|
|
widget = QWidget()
|
|
layout = QFormLayout(widget)
|
|
|
|
self._autostart_cb = QCheckBox()
|
|
self._autostart_cb.setChecked(
|
|
self._working_config.get("general", {}).get("autostart", True)
|
|
)
|
|
self._minimize_cb = QCheckBox()
|
|
self._minimize_cb.setChecked(
|
|
self._working_config.get("general", {}).get("minimize_to_tray", True)
|
|
)
|
|
self._debounce_spin = QSpinBox()
|
|
self._debounce_spin.setRange(100, 5000)
|
|
self._debounce_spin.setSuffix(" ms")
|
|
self._debounce_spin.setValue(
|
|
self._working_config.get("general", {}).get("debounce_ms", 500)
|
|
)
|
|
|
|
layout.addRow("Autostart z Windows:", self._autostart_cb)
|
|
layout.addRow("Minimalizuj do tray przy zamknieciu:", self._minimize_cb)
|
|
layout.addRow("Debounce (opoznienie przelaczenia):", self._debounce_spin)
|
|
|
|
return widget
|
|
|
|
# ── Save ──────────────────────────────────────────────────────────────────
|
|
|
|
def _save_and_close(self) -> None:
|
|
general = self._working_config.setdefault("general", {})
|
|
general["autostart"] = self._autostart_cb.isChecked()
|
|
general["minimize_to_tray"] = self._minimize_cb.isChecked()
|
|
general["debounce_ms"] = self._debounce_spin.value()
|
|
|
|
self._config_manager.update(self._working_config)
|
|
self._hotkey_manager.unregister_all()
|
|
self._hotkey_manager.register_all()
|
|
_set_autostart(general.get("autostart", False))
|
|
self.close()
|
|
|
|
|
|
def _set_autostart(enable: bool) -> None:
|
|
key_path = r"Software\Microsoft\Windows\CurrentVersion\Run"
|
|
app_name = "MonitorSwitcher"
|
|
exe_path = sys.executable if not getattr(sys, "frozen", False) else sys.executable
|
|
try:
|
|
with winreg.OpenKey(
|
|
winreg.HKEY_CURRENT_USER, key_path, 0, winreg.KEY_SET_VALUE
|
|
) as key:
|
|
if enable:
|
|
winreg.SetValueEx(key, app_name, 0, winreg.REG_SZ, f'"{exe_path}"')
|
|
else:
|
|
try:
|
|
winreg.DeleteValue(key, app_name)
|
|
except FileNotFoundError:
|
|
pass
|
|
except Exception as exc:
|
|
logger.warning("Autostart registry error: %s", exc)
|