refactor: use config.json with MVC architecture

This commit is contained in:
2026-05-23 02:52:34 +00:00
parent c8534c967c
commit 38f7ae467a
6 changed files with 96 additions and 183 deletions
+25 -27
View File
@@ -4,9 +4,9 @@ SeniorNet Kiosk — PyQt5 single-row floating toolbar over Google Chrome.
Navigation: ◀ ▶ ⌂ icon buttons + ☰ hamburger menu for bookmarks.
Chrome is controlled via Chrome DevTools Protocol (CDP) over WebSocket.
"""
import sys, json, os, subprocess, time, threading, urllib.request
from PyQt5.QtCore import Qt, QTimer, QPoint, QRect
from PyQt5.QtGui import QFont, QColor, QPalette
import sys, json, os, subprocess, time, threading, urllib.request, websocket, pathlib, shutil, sqlite3
from PyQt5.QtCore import Qt, QTimer, QPoint, QRect, QEvent
from PyQt5.QtGui import QFont, QColor, QPalette, QCursor
from PyQt5.QtWidgets import (
QApplication, QWidget, QHBoxLayout, QVBoxLayout,
QPushButton, QLabel, QScrollArea, QDialog, QLineEdit,
@@ -25,12 +25,18 @@ SCREEN_H = 1080 # overwritten at runtime
# ── Colour palette ────────────────────────────────────────────────
TOOLBAR_COLOR = "#1a3a5c"
BTN_COLOR = "#2e6da4"; BTN_HOVER = "#3a87cc"
GOLD = "#e8a020"; GOLD_HOVER = "#f0b030"
GREEN = "#2e7d32"; GREEN_HOVER = "#388e3c"
RED = "#c62828"; RED_HOVER = "#d32f2f"
AMBER = "#7a5a10"; AMBER_HOVER = "#9a7a20"
PURPLE = "#5a5a8a"; PURPLE_HOVER = "#7a7aaa"
BTN_COLOR = "#2e6da4"
BTN_HOVER = "#3a87cc"
GOLD = "#e8a020"
GOLD_HOVER = "#f0b030"
GREEN = "#2e7d32"
GREEN_HOVER = "#388e3c"
RED = "#c62828"
RED_HOVER = "#d32f2f"
AMBER = "#7a5a10"
AMBER_HOVER = "#9a7a20"
PURPLE = "#5a5a8a"
PURPLE_HOVER = "#7a7aaa"
FONT_SIZE = 18
MENU_STYLE = """
@@ -74,8 +80,10 @@ def mk_icon_btn(icon, color, hover, size=58, fs=26):
def load_bm():
try:
with open(BOOKMARKS_FILE) as f: return json.load(f)
except: return []
with open(BOOKMARKS_FILE) as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return []
def save_bm(data):
with open(BOOKMARKS_FILE, "w") as f: json.dump(data, f, indent=2)
@@ -113,7 +121,6 @@ class CDP:
def _send(self, method, params=None):
if not self.ready:
return
import websocket
with self._lock:
try:
ws = websocket.create_connection(self._ws_url(), timeout=3)
@@ -132,7 +139,6 @@ class CDP:
"""Move and resize the Chrome window via CDP Browser.setWindowBounds."""
if not self.ready:
return
import websocket
with self._lock:
try:
ws = websocket.create_connection(self._ws_url(), timeout=3)
@@ -554,7 +560,6 @@ class BookmarkPanel(QWidget):
self._dismiss_timer.stop()
def _cursor_outside_panel(self):
from PyQt5.QtGui import QCursor
pos = QCursor.pos()
if self.frameGeometry().contains(pos):
return False
@@ -581,7 +586,6 @@ class BookmarkPanel(QWidget):
self.hide()
def eventFilter(self, obj, event):
from PyQt5.QtCore import QEvent
if event.type() == QEvent.MouseButtonPress and self.isVisible():
if not self.frameGeometry().contains(event.globalPos()):
self.hide()
@@ -638,7 +642,6 @@ class KioskOverlay(QWidget):
def changeEvent(self, event):
"""Un-minimise the toolbar the instant it gets iconified."""
from PyQt5.QtCore import QEvent
if event.type() == QEvent.WindowStateChange:
if self.windowState() & Qt.WindowMinimized:
QTimer.singleShot(50, self._self_restore)
@@ -727,7 +730,6 @@ class KioskOverlay(QWidget):
e.accept()
def eventFilter(self, obj, event):
from PyQt5.QtCore import QEvent
if event.type() == QEvent.KeyPress:
key = event.key()
mods = event.modifiers()
@@ -745,13 +747,14 @@ class KioskOverlay(QWidget):
# ── Chrome launcher ───────────────────────────────────────────────
def launch_chrome():
import pathlib, json as _json, shutil
profile_dir = pathlib.Path("/home/jgitta/chromium-kiosk")
profile_dir.mkdir(parents=True, exist_ok=True)
for name in ("SingletonLock", "SingletonSocket", "SingletonCookie"):
try: (profile_dir / name).unlink()
except FileNotFoundError: pass
try:
(profile_dir / name).unlink()
except FileNotFoundError:
pass
for rel in ("GraphiteDawnCache", "GrShaderCache", "ShaderCache",
"OptGuideOnDeviceClassifierModel", "OptGuideOnDeviceModel",
@@ -765,21 +768,16 @@ def launch_chrome():
ls_path = profile_dir / "Local State"
if ls_path.exists():
try:
d = _json.loads(ls_path.read_text())
d = json.loads(ls_path.read_text())
d.setdefault("profile", {})["exit_type"] = "Normal"
d["hardware_acceleration_mode_previous"] = False
ls_path.write_text(_json.dumps(d))
ls_path.write_text(json.dumps(d))
except Exception:
pass
# Clear the stale Roundcube session cookie so the autologin plugin always
# fires cleanly. Without this, Chrome sends the old cookie after a reboot,
# the PHP session in MariaDB has expired, and Roundcube shows
# "session is invalid or expired" instead of auto-logging in.
cookies_db = profile_dir / "Default" / "Cookies"
if cookies_db.exists():
try:
import sqlite3
conn = sqlite3.connect(str(cookies_db))
conn.execute(
"DELETE FROM cookies WHERE host_key LIKE '%mail.jgitta.com%'")