From bf0cdd667ba460ebd1301de7e79b73a360a7a68e Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 16 Aug 2026 16:24:42 -0400 Subject: [PATCH] ui: add custom button navigation actions --- openpilot/common/params_keys.h | 2 +- openpilot/selfdrive/ui/layouts/main.py | 23 ++++++++++++++++-- openpilot/selfdrive/ui/mici/layouts/main.py | 24 +++++++++++++++++-- .../selfdrive/ui/sunnypilot/custom_button.py | 13 +++++----- .../selfdrive/ui/tests/test_custom_button.py | 14 ++++------- .../sunnypilot/sunnylink/settings_ui.json | 14 ++++++++++- .../settings_ui_src/pages/vehicle.yaml | 8 ++++++- 7 files changed, 76 insertions(+), 22 deletions(-) diff --git a/openpilot/common/params_keys.h b/openpilot/common/params_keys.h index c325d4443f..44f21b9761 100644 --- a/openpilot/common/params_keys.h +++ b/openpilot/common/params_keys.h @@ -186,7 +186,7 @@ inline static std::unordered_map keys = { {"ShowTurnSignals", {PERSISTENT | BACKUP, BOOL, "0"}}, {"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}}, {"TrueVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}}, - {"SteeringCustomButtonMapping", {PERSISTENT | BACKUP, INT, "0"}}, + {"CustomButtonAction", {PERSISTENT | BACKUP, INT, "0"}}, // MADS params {"Mads", {PERSISTENT | BACKUP, BOOL, "1"}}, diff --git a/openpilot/selfdrive/ui/layouts/main.py b/openpilot/selfdrive/ui/layouts/main.py index fd26b129e6..89f40af851 100644 --- a/openpilot/selfdrive/ui/layouts/main.py +++ b/openpilot/selfdrive/ui/layouts/main.py @@ -3,7 +3,7 @@ from enum import IntEnum import openpilot.cereal.messaging as messaging from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import Widget -from openpilot.selfdrive.ui.sunnypilot.custom_button import handle_custom_button +from openpilot.selfdrive.ui.sunnypilot.custom_button import CustomButtonAction, handle_custom_button from openpilot.selfdrive.ui.layouts.sidebar import Sidebar, SIDEBAR_WIDTH from openpilot.selfdrive.ui.layouts.home import HomeLayout from openpilot.selfdrive.ui.layouts.settings.settings import SettingsLayout, PanelType @@ -41,6 +41,13 @@ class MainLayout(Widget): MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: AugmentedRoadView(), } + self._custom_button_callbacks = { + CustomButtonAction.BOOKMARK: self._on_bookmark_clicked, + CustomButtonAction.QUIET_MODE: self._toggle_quiet_mode, + CustomButtonAction.ONROAD: self._show_onroad, + CustomButtonAction.HOME: self._show_home, + CustomButtonAction.SETTINGS: self._on_settings_clicked, + } self._sidebar_rect = rl.Rectangle(0, 0, 0, 0) self._content_rect = rl.Rectangle(0, 0, 0, 0) @@ -56,7 +63,7 @@ class MainLayout(Widget): gui_app.push_widget(self._onboarding_window) def _render(self, _): - handle_custom_button(ui_state.sm, ui_state.params, self._on_bookmark_clicked) + handle_custom_button(ui_state.sm, ui_state.params, self._custom_button_callbacks) self._handle_onroad_transition() self._render_main_content() @@ -116,6 +123,18 @@ class MainLayout(Widget): def _on_settings_clicked(self): self.open_settings(PanelType.DEVICE) + @staticmethod + def _toggle_quiet_mode(): + ui_state.params.put_bool('QuietMode', not ui_state.params.get_bool('QuietMode')) + + def _show_onroad(self): + self._set_current_layout(MainState.ONROAD) + self._sidebar.set_visible(False) + + def _show_home(self): + self._set_current_layout(MainState.HOME) + self._sidebar.set_visible(True) + def _on_bookmark_clicked(self): for service in ('bookmarkButton', 'userBookmark'): msg = messaging.new_message(service, valid=True) diff --git a/openpilot/selfdrive/ui/mici/layouts/main.py b/openpilot/selfdrive/ui/mici/layouts/main.py index 84a3f345a4..2e3121df19 100644 --- a/openpilot/selfdrive/ui/mici/layouts/main.py +++ b/openpilot/selfdrive/ui/mici/layouts/main.py @@ -4,7 +4,7 @@ from openpilot.selfdrive.ui.mici.layouts.home import MiciHomeLayout from openpilot.selfdrive.ui.mici.layouts.settings.settings import SettingsLayout from openpilot.selfdrive.ui.mici.layouts.offroad_alerts import MiciOffroadAlerts from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView -from openpilot.selfdrive.ui.sunnypilot.custom_button import handle_custom_button +from openpilot.selfdrive.ui.sunnypilot.custom_button import CustomButtonAction, handle_custom_button from openpilot.selfdrive.ui.ui_state import device, ui_state from openpilot.selfdrive.ui.mici.layouts.onboarding import OnboardingWindow from openpilot.selfdrive.ui.body.layouts.onroad import BodyLayout @@ -36,6 +36,13 @@ class MiciMainLayout(Scroller): self._settings_layout = SettingsLayout() self._car_onroad_layout = AugmentedRoadView(bookmark_callback=self._on_bookmark_clicked) self._body_onroad_layout = BodyLayout() + self._custom_button_callbacks = { + CustomButtonAction.BOOKMARK: self._on_bookmark_clicked, + CustomButtonAction.QUIET_MODE: self._toggle_quiet_mode, + CustomButtonAction.ONROAD: lambda: self._show_layout(self._onroad_layout), + CustomButtonAction.HOME: lambda: self._show_layout(self._home_layout), + CustomButtonAction.SETTINGS: self._show_settings, + } # Initialize widget rects for widget in (self._home_layout, self._alerts_layout, self._settings_layout, @@ -96,7 +103,7 @@ class MiciMainLayout(Scroller): self._alerts_layout._update_state() def _render(self, _): - handle_custom_button(ui_state.sm, ui_state.params, self._on_bookmark_clicked) + handle_custom_button(ui_state.sm, ui_state.params, self._custom_button_callbacks) if not self._setup: if self._alerts_layout.active_alerts() > 0: @@ -153,6 +160,19 @@ class MiciMainLayout(Scroller): msg = messaging.new_message(service, valid=True) self._pm.send(service, msg) + @staticmethod + def _toggle_quiet_mode(): + ui_state.params.put_bool('QuietMode', not ui_state.params.get_bool('QuietMode')) + + def _show_layout(self, layout: Widget): + if gui_app.widget_in_stack(self._onboarding_window): + return + gui_app.pop_widgets_to(self, lambda: self._scroll_to(layout)) + + def _show_settings(self): + if not gui_app.widget_in_stack(self._onboarding_window): + gui_app.push_widget(self._settings_layout) + def _on_body_changed(self): self._car_onroad_layout.set_visible(not ui_state.is_body) self._body_onroad_layout.set_visible(bool(ui_state.is_body)) diff --git a/openpilot/selfdrive/ui/sunnypilot/custom_button.py b/openpilot/selfdrive/ui/sunnypilot/custom_button.py index 20aadff5b1..470b876224 100644 --- a/openpilot/selfdrive/ui/sunnypilot/custom_button.py +++ b/openpilot/selfdrive/ui/sunnypilot/custom_button.py @@ -7,9 +7,12 @@ class CustomButtonAction(IntEnum): NONE = 0 BOOKMARK = 1 QUIET_MODE = 2 + ONROAD = 3 + HOME = 4 + SETTINGS = 5 -def handle_custom_button(sm, params, bookmark_callback): +def handle_custom_button(sm, params, callbacks): if not sm.updated['carState']: return @@ -18,8 +21,6 @@ def handle_custom_button(sm, params, bookmark_callback): if not custom_pressed: return - action = CustomButtonAction(params.get('SteeringCustomButtonMapping', return_default=True)) - if action == CustomButtonAction.BOOKMARK: - bookmark_callback() - elif action == CustomButtonAction.QUIET_MODE: - params.put_bool('QuietMode', not params.get_bool('QuietMode')) + action = CustomButtonAction(params.get('CustomButtonAction', return_default=True)) + if callback := callbacks.get(action): + callback() diff --git a/openpilot/selfdrive/ui/tests/test_custom_button.py b/openpilot/selfdrive/ui/tests/test_custom_button.py index a608d4cb23..9398d18039 100644 --- a/openpilot/selfdrive/ui/tests/test_custom_button.py +++ b/openpilot/selfdrive/ui/tests/test_custom_button.py @@ -17,19 +17,15 @@ class FakeSubMaster: def test_custom_button_actions(): params = Mock() - params.get_bool.return_value = False sm = FakeSubMaster({ 'carState': SimpleNamespace(buttonEvents=[SimpleNamespace( type=car.CarState.ButtonEvent.Type.altButton2, pressed=True, )]), }) - bookmark_callback = Mock() + callbacks = {action: Mock() for action in CustomButtonAction if action != CustomButtonAction.NONE} - params.get.return_value = CustomButtonAction.BOOKMARK - handle_custom_button(sm, params, bookmark_callback) - bookmark_callback.assert_called_once() - - params.get.return_value = CustomButtonAction.QUIET_MODE - handle_custom_button(sm, params, bookmark_callback) - params.put_bool.assert_called_once_with('QuietMode', True) + for action, callback in callbacks.items(): + params.get.return_value = action + handle_custom_button(sm, params, callbacks) + callback.assert_called_once() diff --git a/openpilot/sunnypilot/sunnylink/settings_ui.json b/openpilot/sunnypilot/sunnylink/settings_ui.json index d5fe3f5936..9fc80892fa 100644 --- a/openpilot/sunnypilot/sunnylink/settings_ui.json +++ b/openpilot/sunnypilot/sunnylink/settings_ui.json @@ -2173,7 +2173,7 @@ "description": "", "items": [ { - "key": "SteeringCustomButtonMapping", + "key": "CustomButtonAction", "widget": "multiple_button", "title": "Steering Custom Button", "description": "Choose the openpilot action for the steering wheel custom/star button. OEM functionality is unchanged.", @@ -2189,6 +2189,18 @@ { "value": 2, "label": "Quiet Mode" + }, + { + "value": 3, + "label": "Onroad" + }, + { + "value": 4, + "label": "Home" + }, + { + "value": 5, + "label": "Settings" } ] }, diff --git a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml index e402afe4f6..dcc2cab221 100644 --- a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml +++ b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml @@ -10,7 +10,7 @@ sections: title: Hyundai / Kia / Genesis Settings description: '' items: - - key: SteeringCustomButtonMapping + - key: CustomButtonAction widget: multiple_button title: Steering Custom Button description: Choose the openpilot action for the steering wheel custom/star button. OEM functionality is unchanged. @@ -21,6 +21,12 @@ sections: label: Bookmark - value: 2 label: Quiet Mode + - value: 3 + label: Onroad + - value: 4 + label: Home + - value: 5 + label: Settings - key: HyundaiLongitudinalTuning widget: multiple_button title: Custom Longitudinal Tuning