kiosk: obtain os probe data from ogClient

Define a kiosk event "refresh" to send configuration data from
ogClient to Kiosk. Send the OS list after an ogClient refresh
operation.

Reload widgets in Kiosk when a refresh event is received.
ogkiosk
Alejandro Sirgo Rica 2025-02-05 11:35:28 +01:00
parent 39c97fc1bc
commit 213c9f47a3
8 changed files with 68 additions and 48 deletions

View File

@ -10,6 +10,7 @@ from PyQt6.QtWidgets import (
)
from PyQt6.QtCore import Qt, QSize, pyqtSignal
from PyQt6.QtGui import QPixmap, QIcon
from src.utils.probe import OSFamily
from src.kiosk.config import *
from src.kiosk.theme import *
@ -25,7 +26,6 @@ class BootView(QWidget):
super().__init__()
self.checked_os = None
probe_partitions()
self.layout = QVBoxLayout()
self.layout.setSpacing(10)
@ -67,9 +67,9 @@ class BootView(QWidget):
button.setStyleSheet(f'height: {BUTTON_HEIGHT}px; width: {BUTTON_WIDTH}px;')
button.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextUnderIcon)
button.setCheckable(True)
if part_data['os'] == OSFamily.WINDOWS:
if part_data['os'] == OSFamily.WINDOWS.name:
button.setIcon(win_icon)
elif part_data['os'] == OSFamily.LINUX:
elif part_data['os'] == OSFamily.LINUX.name:
button.setIcon(linux_icon)
button.setIconSize(QSize(BUTTON_ICON_SIZE, BUTTON_ICON_SIZE))
button.toggled.connect(lambda checked, b=button: self.on_part_button_checked(checked, b))

View File

@ -5,8 +5,6 @@
# Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
from src.utils.probe import os_probe, get_os_family, OSFamily
from src.utils.fs import mount_mkdir, umount
from PyQt6.QtGui import QColor
from src.utils.disk import *
from src.utils.net import *
@ -16,39 +14,13 @@ import os
config_data = {}
def update_config(new_config):
for k in new_config:
config_data[k] = new_config[k]
def get_boot_os_list():
return config_data.get('os_list', [])
def probe_partitions():
os_list = []
for disknum, disk in enumerate(get_disks(), start=1):
try:
partitions = get_partition_data(device=f'/dev/{disk}')
except Exception as e:
continue
for pa in partitions:
partnum = pa.partno + 1
mountpoint = pa.padev.replace('dev', 'mnt')
if mount_mkdir(pa.padev, mountpoint):
try:
os_family = get_os_family(mountpoint)
if os_family == OSFamily.UNKNOWN:
continue
os_name = os_probe(mountpoint)
os_data = {
'name': os_name,
'partition': partnum,
'disk': disknum,
'os': os_family,
'image': ''
}
os_list.append(os_data)
finally:
umount(mountpoint)
config_data['os_list'] = os_list
def get_main_color_theme():
color_values = config_data.get('color_theme', [49, 69, 106])
return QColor(*color_values)

View File

@ -51,7 +51,8 @@ class Kiosk(QMainWindow):
self.main_content.setLayout(QVBoxLayout())
layout.addWidget(self.main_content)
self.reload_theme()
self.instance_sidepanel()
self.side_panel.request_widget_change(ViewType.BOOT_OS)
def closeEvent(self, event):
if self.socket_notifier:
@ -82,6 +83,8 @@ class Kiosk(QMainWindow):
status = payload.get('status', 'idle')
self.apply_busy_mode_configuration(status)
elif payload.get('command') == 'refresh':
new_configuration = payload.get('config', {})
update_config(new_configuration)
self.reload_theme()
elif payload.get('command') == 'close':
self.close()
@ -90,24 +93,23 @@ class Kiosk(QMainWindow):
except json.JSONDecodeError as e:
logging.error(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()
def instance_sidepanel(self):
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 reload_theme(self):
self.side_panel.reload_theme()
if self.side_panel.last_widget == ViewType.BOOT_OS:
self.side_panel.request_widget_change(ViewType.BOOT_OS)
def apply_busy_mode_configuration(self, status):
if get_status() == status:
return

View File

@ -56,6 +56,8 @@ class SidePanel(QFrame):
super().__init__()
self.panel_hidden = False
self.last_widget = ViewType.BOOT_OS
self.button_map = {}
self.setFrameStyle(QFrame.Shape.StyledPanel | QFrame.Shadow.Plain)
@ -71,11 +73,12 @@ class SidePanel(QFrame):
layout.addWidget(button)
button.clicked.connect(lambda _, view=view_type: self.request_widget_change(view))
self.button_map[view_type] = button
def reload_theme(self):
for view_type, button in self.button_map.items():
button.setIcon(view_type.get_view_icon())
def request_widget_change(self, view_type):
self.last_widget = view_type
self.widget_change_requested.emit(view_type.get_widget_instance())
def emit_default_widget(self):
self.request_widget_change(ViewType.BOOT_OS)
def emit_monitor_widget(self):
self.request_widget_change(ViewType.SYSTEM_MONITOR)

27
src/live/kiosk.py 100644
View File

@ -0,0 +1,27 @@
#
# 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.
kiosk_config = {}
def update_kiosk_config(refresh_payload):
from src.utils.probe import OSFamily
os_list = []
for part_setup in refresh_payload['partition_setup']:
if part_setup['os_family'] == OSFamily.UNKNOWN.name:
del part_setup['os_family']
continue
os_data = {
'name': part_setup['os'],
'partition': int(part_setup['partition']),
'disk': int(part_setup['disk']),
'os': part_setup['os_family'],
'image': ''
}
del part_setup['os_family']
os_list.append(os_data)
kiosk_config['os_list'] = os_list

View File

@ -23,6 +23,7 @@ from src.live.partcodes import GUID_MAP
from src.live.parttypes import get_parttype
from src.utils.image import *
from src.live.kiosk import *
from src.utils.postinstall import configure_os
from src.utils.net import *
from src.utils.menu import generate_menu
@ -83,9 +84,12 @@ class OgLiveOperations:
if mount_mkdir(pa.padev, target):
part_setup['os'] = ''
part_setup['os_family'] = OSFamily.UNKNOWN.name
if part_setup['disk'] == '1':
probe_result = os_probe(target)
part_setup['os'] = probe_result
os_family = get_os_family(target)
part_setup['os_family'] = os_family.name
total, used, free = shutil.disk_usage(target)
part_setup['used_size'] = used
@ -93,6 +97,7 @@ class OgLiveOperations:
umount(target)
else:
part_setup['os'] = ''
part_setup['os_family'] = OSFamily.UNKNOWN.name
part_setup['used_size'] = 0
part_setup['free_size'] = 0
@ -861,6 +866,7 @@ class OgLiveOperations:
if boot_entry_data:
json_body['efi'] = boot_entry_data
update_kiosk_config(json_body)
generate_menu(json_body['partition_setup'])
generate_cache_txt()
self._restartBrowser(self._url)

View File

@ -16,6 +16,7 @@ import logging
from io import StringIO
from src.restRequest import *
from src.live.kiosk import *
from src.ogRest import *
from src.log import OgError
from enum import Enum
@ -159,6 +160,12 @@ class ogClient:
except Exception as e:
logging.error(f"Unexpected error in send_kiosk_event: {e}")
def send_kiosk_refresh(self):
self.send_kiosk_event({
'command': 'refresh',
'config': kiosk_config
})
def cleanup(self):
self.data = ""
self.content_len = 0

View File

@ -176,6 +176,7 @@ class ogThread():
response = restResponse(ogResponses.OK, json_body, seq=client.seq)
client.send(response.get())
client.send_kiosk_refresh()
ogRest.set_state(ThreadState.IDLE, client)
def image_restore_local(client, request, ogRest):
@ -200,6 +201,7 @@ class ogThread():
response = restResponse(ogResponses.OK, json_body, seq=client.seq)
client.send(response.get())
client.send_kiosk_refresh()
ogRest.set_state(ThreadState.IDLE, client)
def image_create(client, request, ogRest):
@ -271,6 +273,7 @@ class ogThread():
response = restResponse(ogResponses.OK, json_body, seq=client.seq)
client.send(response.get())
client.send_kiosk_refresh()
ogRest.set_state(ThreadState.IDLE, client, update_kiosk=False)
class ogResponses(Enum):