@@ -82,6 +82,7 @@ class AdminTab(QWidget):
8282
8383 reconnect_expected = Signal ()
8484 reconnect_prepare = Signal () # prepare monitor + pause polling (ISP check starting)
85+ flash_sb2_requested = Signal (str ) # path to .sb2 — handled by overview tab
8586 isp_done = Signal () # ISP check finished — resume monitor polling
8687 variant_detected = Signal (str ) # forwarded to overview tab
8788 _check_variant_requested = Signal ()
@@ -193,13 +194,7 @@ def _setup_ui(self) -> None:
193194 self ._factory_reset_hint_label .setVisible (False )
194195 danger_layout .addWidget (self ._factory_reset_hint_label )
195196
196- danger_btn_layout = QHBoxLayout ()
197- self ._factory_reset_btn = QPushButton ("Factory Reset" )
198- self ._factory_reset_btn .setToolTip (
199- "Unplug and re-plug the device, start reset within about 10 seconds, then touch to confirm."
200- )
201- self ._factory_reset_btn .clicked .connect (self ._factory_reset )
202- self ._factory_reset_btn .setStyleSheet (f"""
197+ danger_btn_style = f"""
203198 QPushButton {{
204199 color: { danger_colors ['title_text' ]} ;
205200 border: 1px solid { danger_colors ['border' ]} ;
@@ -215,8 +210,24 @@ def _setup_ui(self) -> None:
215210 border-color: { danger_colors ['button_disabled_border' ]} ;
216211 color: { danger_colors ['button_disabled_text' ]} ;
217212 }}
218- """ )
213+ """
214+ danger_btn_layout = QHBoxLayout ()
215+ self ._factory_reset_btn = QPushButton ("Factory Reset" )
216+ self ._factory_reset_btn .setToolTip (
217+ "Unplug and re-plug the device, start reset within about 10 seconds, then touch to confirm."
218+ )
219+ self ._factory_reset_btn .clicked .connect (self ._factory_reset )
220+ self ._factory_reset_btn .setStyleSheet (danger_btn_style )
219221 danger_btn_layout .addWidget (self ._factory_reset_btn )
222+ self ._flash_sb2_btn = QPushButton ("Flash Signed Firmware (.sb2)…" )
223+ self ._flash_sb2_btn .setToolTip (
224+ "Flash a signed SB2.1 firmware file (.sb2) from disk.\n "
225+ "Works on all variants, including Secure devices — the bootloader\n "
226+ "rejects files that are not correctly signed."
227+ )
228+ self ._flash_sb2_btn .clicked .connect (self ._flash_sb2_from_file )
229+ self ._flash_sb2_btn .setStyleSheet (danger_btn_style )
230+ danger_btn_layout .addWidget (self ._flash_sb2_btn )
220231 self ._restart_admin_button = QPushButton ("Restart as Administrator" )
221232 self ._restart_admin_button .clicked .connect (partial (restart_as_admin_from_ui , self ))
222233 self ._restart_admin_button .setVisible (False )
@@ -315,6 +326,7 @@ def _set_controls_enabled(self, enabled: bool) -> None:
315326 self ._unlock_btn .setEnabled (enabled and self ._last_isp_variant == "Hacker (locked)" )
316327 self ._reboot_regular_btn .setEnabled (enabled )
317328 self ._reboot_bootloader_btn .setEnabled (enabled )
329+ self ._flash_sb2_btn .setEnabled (enabled )
318330 self ._update_factory_reset_controls (enabled )
319331
320332 def _apply_capabilities (self , caps ) -> None :
@@ -324,6 +336,7 @@ def _apply_capabilities(self, caps) -> None:
324336 self ._unlock_btn .setEnabled (can_boot and self ._last_isp_variant == "Hacker (locked)" )
325337 self ._reboot_regular_btn .setEnabled (has_device and caps .has_reboot )
326338 self ._reboot_bootloader_btn .setEnabled (can_boot )
339+ self ._flash_sb2_btn .setEnabled (can_boot )
327340 self ._update_factory_reset_controls (has_device )
328341
329342 def _apply_factory_reset_hint_style (self ) -> None :
@@ -446,6 +459,32 @@ def _unlock_device(self) -> None:
446459 self ._show_touch_prompt ()
447460 self ._unlock_requested .emit ("" )
448461
462+ def _flash_sb2_from_file (self ) -> None :
463+ reply = QMessageBox .warning (
464+ self ,
465+ "Flash Signed Firmware" ,
466+ "This flashes a signed SB2.1 firmware file (.sb2), like the ones "
467+ "attached to official GitHub releases.\n \n "
468+ "Signed firmware can be installed on any variant, including Secure "
469+ "devices — the bootloader rejects files that are not correctly signed.\n \n "
470+ "After you select a file, watch the Solo 2 and press its button when "
471+ "it asks for touch confirmation to enter bootloader mode.\n \n "
472+ "Do not disconnect the device during the process.\n \n "
473+ "Continue?" ,
474+ QMessageBox .Yes | QMessageBox .No ,
475+ QMessageBox .No ,
476+ )
477+ if reply != QMessageBox .Yes :
478+ return
479+
480+ path , _ = QFileDialog .getOpenFileName (
481+ self , "Select Signed Firmware" , "" , "Signed Firmware (*.sb2);;All Files (*)"
482+ )
483+ if not path :
484+ return
485+
486+ self .flash_sb2_requested .emit (path )
487+
449488 def _reboot (self , mode : RebootMode ) -> None :
450489 mode_name = "bootloader" if mode == RebootMode .BOOTLOADER else "normal"
451490 reply = QMessageBox .question (
0 commit comments