mirror of https://git.48k.eu/ogclient
218 lines
7.4 KiB
Python
218 lines
7.4 KiB
Python
# Copyright (C) 2020-2025 Soleta Networks <info@soleta.eu>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it under
|
|
# the terms of the GNU Affero General Public License as published by the
|
|
# Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
|
|
from PyQt6.QtCore import (
|
|
Qt, QPropertyAnimation, QRect, QFile, QTextStream, QTranslator,
|
|
QLocale, QSocketNotifier
|
|
)
|
|
from PyQt6.QtWidgets import (
|
|
QApplication, QMainWindow, QWidget, QHBoxLayout, QVBoxLayout
|
|
)
|
|
import sys
|
|
|
|
from src.kiosk.sidepanel import SidePanel, ViewType
|
|
import src.kiosk.thirdparty.breeze.breeze_pyqt6
|
|
from src.kiosk.spinner import *
|
|
from src.kiosk.config import *
|
|
|
|
|
|
SIDE_PANEL_WIDTH = 200
|
|
|
|
|
|
class Kiosk(QMainWindow):
|
|
def __init__(self, socket):
|
|
super().__init__()
|
|
self.side_panel = None
|
|
self.spinner = None
|
|
|
|
# socket to read/send events from/to ogClient
|
|
self.socket = socket
|
|
if socket:
|
|
self.socket_notifier = QSocketNotifier(
|
|
self.socket.fileno(), QSocketNotifier.Type.Read
|
|
)
|
|
self.socket_notifier.activated.connect(self.read_from_socket)
|
|
|
|
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
|
|
|
self.setWindowTitle('Kiosk')
|
|
|
|
central_widget = QWidget()
|
|
self.setCentralWidget(central_widget)
|
|
layout = QHBoxLayout()
|
|
central_widget.setLayout(layout)
|
|
|
|
self.main_content = QWidget()
|
|
self.main_content.setLayout(QVBoxLayout())
|
|
layout.addWidget(self.main_content)
|
|
|
|
self.reload_theme()
|
|
|
|
def closeEvent(self, event):
|
|
if self.socket_notifier:
|
|
self.socket_notifier.setEnabled(False)
|
|
self.socket_notifier.disconnect()
|
|
self.socket_notifier.deleteLater()
|
|
if self.socket:
|
|
self.socket.close()
|
|
super().closeEvent(event)
|
|
|
|
def send_to_ogclient(self, data):
|
|
try:
|
|
json_data = json.dumps(data)
|
|
self.socket.send(json_data.encode('utf-8'))
|
|
except TypeError as e:
|
|
print(f'Failed to encode data to JSON: {e}')
|
|
except Exception as e:
|
|
print(f'Unexpected error in send_kiosk_event: {e}')
|
|
|
|
def read_from_socket(self):
|
|
try:
|
|
data = self.socket.recv(1024).decode('utf-8')
|
|
if not data:
|
|
return # Socket closed
|
|
payload = json.loads(data)
|
|
|
|
if payload.get('command') == 'state':
|
|
status = payload.get('status', 'idle')
|
|
self.apply_busy_mode_configuration(status)
|
|
elif payload.get('command') == 'refresh':
|
|
self.reload_theme()
|
|
elif payload.get('command') == 'close':
|
|
self.close()
|
|
except OSError as e:
|
|
print(f'Failed to read from socket: {e}')
|
|
except json.JSONDecodeError as e:
|
|
print(f'Failed to decode JSON: {e}')
|
|
|
|
def reload_theme(self):
|
|
# Floating side panel
|
|
if self.side_panel:
|
|
self.side_panel.setParent(None)
|
|
self.side_panel.deleteLater()
|
|
self.side_panel = SidePanel()
|
|
self.side_panel.setParent(self)
|
|
self.side_panel.setFixedWidth(SIDE_PANEL_WIDTH)
|
|
self.side_panel.panel_hidden = True
|
|
self.side_panel.setGeometry(-SIDE_PANEL_WIDTH, 0, SIDE_PANEL_WIDTH, self.height())
|
|
self.side_panel.widget_change_requested.connect(self.update_view)
|
|
self.side_panel.emit_default_widget()
|
|
self.side_panel.setVisible(True)
|
|
self.side_panel.raise_()
|
|
|
|
self.animation = QPropertyAnimation(self.side_panel, b'geometry')
|
|
self.animation.setDuration(200)
|
|
|
|
def apply_busy_mode_configuration(self, status):
|
|
if get_status() == status:
|
|
return
|
|
|
|
set_status(status)
|
|
if is_busy_enabled():
|
|
self.toggle_panel()
|
|
self.side_panel.request_widget_change(ViewType.SYSTEM_MONITOR)
|
|
|
|
self.spinner = LoadSpinner(self)
|
|
self.spinner.setHeight(30)
|
|
self.spinner.setColor(get_main_color_theme())
|
|
self.spinner.start()
|
|
self.spinner.show()
|
|
self.spinner.setToolTip(self.tr('The client is busy, please wait...'))
|
|
return
|
|
|
|
if self.spinner:
|
|
self.spinner.setParent(None)
|
|
self.spinner.deleteLater()
|
|
|
|
def keyPressEvent(self, event):
|
|
if side_panel_enabled() and event.key() == Qt.Key.Key_F1:
|
|
self.side_panel.request_widget_change(ViewType.SYSTEM_MONITOR)
|
|
elif side_panel_enabled() and event.key() == Qt.Key.Key_F2:
|
|
self.side_panel.request_widget_change(ViewType.BOOT_OS)
|
|
elif event.key() == Qt.Key.Key_P:
|
|
self.toggle_panel()
|
|
else:
|
|
super().keyPressEvent(event)
|
|
|
|
def toggle_panel(self):
|
|
if self.side_panel.panel_hidden and side_panel_enabled():
|
|
# Show the panel
|
|
self.animation.setStartValue(self.side_panel.geometry())
|
|
self.animation.setEndValue(QRect(0, 0, SIDE_PANEL_WIDTH, self.height()))
|
|
self.side_panel.panel_hidden = False
|
|
else:
|
|
# Hide the panel
|
|
self.animation.setStartValue(self.side_panel.geometry())
|
|
self.animation.setEndValue(QRect(-SIDE_PANEL_WIDTH, 0, SIDE_PANEL_WIDTH, self.height()))
|
|
self.side_panel.panel_hidden = True
|
|
|
|
self.animation.start()
|
|
|
|
def resizeEvent(self, event):
|
|
super().resizeEvent(event)
|
|
|
|
# Adjust panel geometry based on visibility
|
|
if self.side_panel.panel_hidden:
|
|
self.side_panel.setGeometry(-SIDE_PANEL_WIDTH, 0, SIDE_PANEL_WIDTH, self.height())
|
|
else:
|
|
self.side_panel.setGeometry(0, 0, SIDE_PANEL_WIDTH, self.height())
|
|
|
|
def update_view(self, widget):
|
|
layout = self.main_content.layout()
|
|
|
|
# Clear existing widgets
|
|
for i in reversed(range(layout.count())):
|
|
widget_item = layout.itemAt(i).widget()
|
|
if widget_item:
|
|
widget_item.setParent(None)
|
|
widget_item.deleteLater()
|
|
|
|
layout.addWidget(widget)
|
|
widget.command_requested.connect(self.send_to_ogclient)
|
|
|
|
def closeEvent(self, event):
|
|
logging.info("Closing Kiosk application...")
|
|
if self.socket_notifier:
|
|
self.socket_notifier.setEnabled(False)
|
|
if self.socket:
|
|
self.socket.close()
|
|
event.accept()
|
|
|
|
def launch_kiosk(socket, debug_mode):
|
|
app = QApplication(sys.argv)
|
|
|
|
translator = QTranslator()
|
|
locale = QLocale.system().name()
|
|
translation_file = f'kiosk_{locale}.qm'
|
|
translations_dir = os.path.join(
|
|
os.path.dirname(__file__),
|
|
'translations')
|
|
|
|
if translator.load(translation_file, translations_dir):
|
|
app.installTranslator(translator)
|
|
|
|
file = QFile(':/light/stylesheet.qss')
|
|
file.open(QFile.OpenModeFlag.ReadOnly | QFile.OpenModeFlag.Text)
|
|
stream = QTextStream(file)
|
|
app.setStyleSheet(stream.readAll())
|
|
|
|
gui = Kiosk(socket)
|
|
if debug_mode:
|
|
gui.showMaximized()
|
|
else:
|
|
gui.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.Window)
|
|
gui.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground, True)
|
|
gui.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
|
|
gui.setAttribute(Qt.WidgetAttribute.WA_OpaquePaintEvent, True)
|
|
|
|
screen = QApplication.primaryScreen()
|
|
geometry = screen.availableGeometry()
|
|
gui.setGeometry(geometry)
|
|
gui.show()
|
|
|
|
return app.exec()
|