# tsk/c4/tsk_manager.py """ TSK Manager main application for C4 (mici) device. Features: - Fixed top banner showing key installation status (clickable) - Vertical scroller with two horizontal scrollers (one for each row of buttons) Uses custom button with BigButton graphics that scales properly. """ import pyray as rl from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import Widget from tsk.common.widget import Scroller from tsk.c4.menu_0_tools.btn_0_extractor import Extractor from tsk.c4.menu_0_tools.btn_1_keyboard import Keyboard from tsk.c4.menu_0_tools.btn_2_uninstaller import Uninstaller from tsk.c4.menu_0_tools.btn_3_guide import Guide from tsk.c4.menu_1_reboot.btn_0_recommended import Recommended from tsk.c4.menu_1_reboot.btn_1_alternate import Alternate from tsk.c4.menu_1_reboot.btn_2_somethingelse import SomethingElse from tsk.c4.menu_1_reboot.btn_3_reboot import Reboot from tsk.c4.ui import Layout, ScrollableBigDialog from tsk.common.key_file_manager import KeyFileManager from tsk.common.widget import TSKWidget class KeyStatusBanner(Widget): """ Top banner showing key installation status. Acts as a button - click to see details. """ def __init__(self, key_manager: KeyFileManager): super().__init__() self._key_manager = key_manager self.set_rect(rl.Rectangle(0, 0, gui_app.width, Layout.banner_height)) def _get_status_text(self) -> str: """Get the current key status text.""" if self._key_manager.installed_key: return "Key installed" else: return "Key not installed" def _handle_mouse_release(self, mouse_pos): """Handle click on the banner.""" if self._key_manager.installed_key: # Show the installed key self._show_installed_key_dialog() else: # Show instruction to run TSK Extractor self._show_no_key_dialog() return True def _show_no_key_dialog(self): """Show dialog when no key is installed.""" dialog = ScrollableBigDialog( description="Run TSK Extractor to get your key" ) gui_app.push_widget(dialog) def _show_installed_key_dialog(self): """Show dialog displaying the installed key.""" exploded_key = ( self._key_manager.installed_key[0:4] + ' ' + self._key_manager.installed_key[4:8] + ' ' + self._key_manager.installed_key[8:12] + ' ' + self._key_manager.installed_key[12:16] + '\n' + self._key_manager.installed_key[16:20] + ' ' + self._key_manager.installed_key[20:24] + ' ' + self._key_manager.installed_key[24:28] + ' ' + self._key_manager.installed_key[28:32] ) dialog = ScrollableBigDialog( title='Installed Key', description=exploded_key ) gui_app.push_widget(dialog) def _render(self, rect: rl.Rectangle): """Render the key status banner.""" # Background color: dark gray, slightly lighter when pressed bg_color = rl.Color(60, 60, 60, 255) if self.is_pressed else rl.Color(50, 50, 50, 255) rl.draw_rectangle_rec(rect, bg_color) # Draw status text (centered) status_text = self._get_status_text() font = gui_app.font(FontWeight.MEDIUM) font_size = 28 text_size = rl.measure_text_ex(font, status_text, font_size, 0) text_x = rect.x + (rect.width - text_size.x) / 2 text_y = rect.y + (rect.height - text_size.y) / 2 # Color: green if key installed, yellow if not text_color = rl.Color(100, 255, 100, 255) if self._key_manager.installed_key else rl.Color(255, 200, 0, 255) rl.draw_text_ex(font, status_text, rl.Vector2(text_x, text_y), font_size, 0, text_color) # Draw a subtle bottom border rl.draw_line(int(rect.x), int(rect.y + rect.height - 1), int(rect.x + rect.width), int(rect.y + rect.height - 1), rl.Color(80, 80, 80, 255)) return True class TSKManager(TSKWidget): """ TSK Manager for C4 (mici) device. Layout: - Fixed top banner: Key status (clickable) - Vertical scroller with two horizontal scrollers (one for each row) """ def __init__(self): super().__init__() # Initialize key manager self.key_manager = KeyFileManager() # Create top banner (fixed, not scrollable) self.key_banner = KeyStatusBanner(self.key_manager) # Create two horizontal scrollers self.tools_scroller = Scroller( [ Extractor(), # Keyboard(), Uninstaller(), Guide(), ], horizontal=True, snap_items=False, spacing=Layout.scroller_spacing, pad=Layout.scroller_padding, scroll_indicator=False ) self.tools_scroller.set_rect(rl.Rectangle(0, 0, gui_app.width, Layout.button_height)) self.reboot_scroller = Scroller( [ Recommended(), Alternate(), SomethingElse(), Reboot(), ], horizontal=True, snap_items=False, spacing=Layout.scroller_spacing, pad=Layout.scroller_padding, scroll_indicator=False ) self.reboot_scroller.set_rect(rl.Rectangle(0, 0, gui_app.width, Layout.button_height)) # Create vertical scroller with the two horizontal scrollers self.vertical_scroller = Scroller( [self.tools_scroller, self.reboot_scroller], horizontal=False, snap_items=True, spacing=Layout.scroller_spacing, pad=Layout.scroller_padding ) # Propagate enabled state so children stop processing input # when a dialog is pushed on top (push_widget disables us) self.key_banner.set_enabled(lambda: self.enabled) self.vertical_scroller.set_enabled(lambda: self.enabled) self.tools_scroller.set_enabled(lambda: self.enabled) self.reboot_scroller.set_enabled(lambda: self.enabled) def _render(self, rect: rl.Rectangle): """Render the C4 GUI.""" # Enable scissor mode for the scroller area to clip overflow scroller_rect = rl.Rectangle( rect.x, rect.y + Layout.banner_height, rect.width, rect.height - Layout.banner_height ) rl.begin_scissor_mode(int(scroller_rect.x), int(scroller_rect.y), int(scroller_rect.width), int(scroller_rect.height)) # Render the vertical scroller self.vertical_scroller.render(scroller_rect) rl.end_scissor_mode() # Render the banner AFTER the scroller so it draws on top banner_rect = rl.Rectangle(rect.x, rect.y, rect.width, Layout.banner_height) self.key_banner.render(banner_rect) return True